Merge "Camera Extensions: Add OutputSurfaceConfiguration class to retrieve surface outputs"
diff --git a/.clang-format b/.clang-format
index 03af56d..d60d33c 100644
--- a/.clang-format
+++ b/.clang-format
@@ -9,5 +9,17 @@
ConstructorInitializerIndentWidth: 6
ContinuationIndentWidth: 8
IndentWidth: 4
+JavaImportGroups:
+- android
+- androidx
+- com.android
+- dalvik
+- libcore
+- com
+- junit
+- net
+- org
+- java
+- javax
PenaltyBreakBeforeFirstCallParameter: 100000
SpacesBeforeTrailingComments: 1
diff --git a/Android.bp b/Android.bp
index 0a14565..4e7eba2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -95,8 +95,11 @@
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.hardware.biometrics.common-V3-java-source",
+ ":android.hardware.biometrics.fingerprint-V3-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
+ ":android.hardware.keymaster-V4-java-source",
":android.hardware.security.keymint-V2-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.tv.tuner-V1-java-source",
@@ -213,13 +216,13 @@
"android.hardware.radio-V1.4-java",
"android.hardware.radio-V1.5-java",
"android.hardware.radio-V1.6-java",
- "android.hardware.radio.data-V1-java",
+ "android.hardware.radio.data-V2-java",
"android.hardware.radio.ims-V1-java",
- "android.hardware.radio.messaging-V1-java",
- "android.hardware.radio.modem-V1-java",
+ "android.hardware.radio.messaging-V2-java",
+ "android.hardware.radio.modem-V2-java",
"android.hardware.radio.network-V2-java",
- "android.hardware.radio.sim-V1-java",
- "android.hardware.radio.voice-V1-java",
+ "android.hardware.radio.sim-V2-java",
+ "android.hardware.radio.voice-V2-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
@@ -332,7 +335,10 @@
"packages/modules/Bluetooth/framework/aidl-export",
"packages/modules/Connectivity/framework/aidl-export",
"packages/modules/Media/apex/aidl/stable",
+ "hardware/interfaces/biometrics/common/aidl",
+ "hardware/interfaces/biometrics/fingerprint/aidl",
"hardware/interfaces/graphics/common/aidl",
+ "hardware/interfaces/keymaster/aidl",
],
},
dxflags: [
@@ -617,7 +623,10 @@
"packages/modules/Bluetooth/framework/aidl-export",
"packages/modules/Connectivity/framework/aidl-export",
"packages/modules/Media/apex/aidl/stable",
+ "hardware/interfaces/biometrics/common/aidl",
+ "hardware/interfaces/biometrics/fingerprint/aidl",
"hardware/interfaces/graphics/common/aidl",
+ "hardware/interfaces/keymaster/aidl",
],
},
// These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 6a323d6..bf3a6a3 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -84,7 +84,6 @@
":framework-connectivity-sources",
":framework-bluetooth-sources",
":framework-connectivity-tiramisu-updatable-sources",
- ":framework-federatedcompute-sources",
":framework-graphics-srcs",
":framework-mediaprovider-sources",
":framework-nearby-sources",
diff --git a/GAME_MANAGER_OWNERS b/GAME_MANAGER_OWNERS
index 502a9e36..b65c43a 100644
--- a/GAME_MANAGER_OWNERS
+++ b/GAME_MANAGER_OWNERS
@@ -1,2 +1,3 @@
lpy@google.com
-timvp@google.com
+chingtangyu@google.com
+xwxw@google.com
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 98e4f45..9366ff2d 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -62,4 +62,10 @@
test_suites: ["device-tests"],
certificate: "platform",
+
+ errorprone: {
+ javacflags: [
+ "-Xep:ReturnValueIgnored:WARN",
+ ],
+ },
}
diff --git a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
index 2ef68ca..05a3e12 100644
--- a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
@@ -118,7 +118,7 @@
int got = count.get();
if (n != got) {
throw new IllegalStateException(
- String.format("Only %i of %i objects finalized?", got, n));
+ String.format("Only %d of %d objects finalized?", got, n));
}
}
}
diff --git a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
index 0802072..0cce6ad 100644
--- a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
@@ -18,11 +18,11 @@
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
import android.provider.Settings;
import android.view.Display;
-import androidx.benchmark.BenchmarkState;
-import androidx.benchmark.junit4.BenchmarkRule;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -38,7 +38,7 @@
private static final float DELTA = 0.001f;
@Rule
- public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+ public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private DisplayManager mDisplayManager;
private Context mContext;
@@ -51,7 +51,7 @@
@Test
public void testBrightnessChanges() throws Exception {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 63e5983..5befa1f 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -35,4 +35,8 @@
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.multiuser"/>
+ <queries>
+ <package android:name="perftests.multiuser.apps.dummyapp" />
+ </queries>
+
</manifest>
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index f59e7a4..652c49a 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -16,11 +16,13 @@
package android.app;
+import android.annotation.NonNull;
import android.app.job.IJobScheduler;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
+import android.content.Context;
import android.os.RemoteException;
import java.util.List;
@@ -36,8 +38,10 @@
*/
public class JobSchedulerImpl extends JobScheduler {
IJobScheduler mBinder;
+ private final Context mContext;
- public JobSchedulerImpl(IJobScheduler binder) {
+ public JobSchedulerImpl(@NonNull Context context, IJobScheduler binder) {
+ mContext = context;
mBinder = binder;
}
@@ -103,6 +107,24 @@
}
@Override
+ public boolean canRunLongJobs() {
+ try {
+ return mBinder.canRunLongJobs(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean hasRunLongJobsPermission(String packageName, int userId) {
+ try {
+ return mBinder.hasRunLongJobsPermission(packageName, userId);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ @Override
public List<JobInfo> getStartedJobs() {
try {
return mBinder.getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index d281da0..a3390b7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -30,6 +30,25 @@
*/
interface IJobCallback {
/**
+ * Immediate callback to the system after sending a data transfer download progress request
+ * signal; used to quickly detect ANR.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param workId Unique integer used to identify a specific work item.
+ * @param transferredBytes How much data has been downloaded, in bytes.
+ */
+ void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+ long transferredBytes);
+ /**
+ * Immediate callback to the system after sending a data transfer upload progress request
+ * signal; used to quickly detect ANR.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param workId Unique integer used to identify a specific work item.
+ * @param transferredBytes How much data has been uploaded, in bytes.
+ */
+ void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId, long transferredBytes);
+ /**
* Immediate callback to the system after sending a start signal, used to quickly detect ANR.
*
* @param jobId Unique integer used to identify this job.
@@ -65,4 +84,24 @@
*/
@UnsupportedAppUsage
void jobFinished(int jobId, boolean reschedule);
+ /*
+ * Inform JobScheduler of a change in the estimated transfer payload.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param item The particular JobWorkItem this progress is associated with, if any.
+ * @param downloadBytes How many bytes the app expects to download.
+ * @param uploadBytes How many bytes the app expects to upload.
+ */
+ void updateEstimatedNetworkBytes(int jobId, in JobWorkItem item,
+ long downloadBytes, long uploadBytes);
+ /*
+ * Update JobScheduler of how much data the job has successfully transferred.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param item The particular JobWorkItem this progress is associated with, if any.
+ * @param transferredDownloadBytes The number of bytes that have successfully been downloaded.
+ * @param transferredUploadBytes The number of bytes that have successfully been uploaded.
+ */
+ void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
+ long transferredDownloadBytes, long transferredUploadBytes);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index 3006f50..d2be32e 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -33,6 +33,8 @@
void cancelAll();
ParceledListSlice getAllPendingJobs();
JobInfo getPendingJob(int jobId);
+ boolean canRunLongJobs(String packageName);
+ boolean hasRunLongJobsPermission(String packageName, int userId);
List<JobInfo> getStartedJobs();
ParceledListSlice getAllJobSnapshots();
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
index 22ad252..2bb82bd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
@@ -17,6 +17,7 @@
package android.app.job;
import android.app.job.JobParameters;
+import android.app.job.JobWorkItem;
/**
* Interface that the framework uses to communicate with application code that implements a
@@ -31,4 +32,8 @@
/** Stop execution of application's job. */
@UnsupportedAppUsage
void stopJob(in JobParameters jobParams);
+ /** Update JS of how much data has been downloaded. */
+ void getTransferredDownloadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
+ /** Update JS of how much data has been uploaded. */
+ void getTransferredUploadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 4c849fe..9caf99e 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -396,6 +396,13 @@
public static final int FLAG_EXPEDITED = 1 << 4;
/**
+ * Whether it's a data transfer job or not.
+ *
+ * @hide
+ */
+ public static final int FLAG_DATA_TRANSFER = 1 << 5;
+
+ /**
* @hide
*/
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -723,6 +730,14 @@
}
/**
+ * @see JobInfo.Builder#setDataTransfer(boolean)
+ * @hide
+ */
+ public boolean isDataTransfer() {
+ return (flags & FLAG_DATA_TRANSFER) != 0;
+ }
+
+ /**
* @see JobInfo.Builder#setImportantWhileForeground(boolean)
*/
public boolean isImportantWhileForeground() {
@@ -1816,6 +1831,52 @@
}
/**
+ * Indicates that this job will be used to transfer data to or from a remote server. The
+ * system could attempt to run a data transfer job longer than a regular job if the data
+ * being transferred is potentially very large and can take a long time to complete.
+ *
+ * <p>
+ * The app must hold the {@link android.Manifest.permission#RUN_LONG_JOBS} permission to
+ * use this API. JobScheduler will throw a {@link SecurityException} if an app without the
+ * permission granted attempts to schedule a data transfer job.
+ *
+ * <p>
+ * You must provide an estimate of the payload size via
+ * {@link #setEstimatedNetworkBytes(long, long)} when scheduling the job or use
+ * {@link JobService#updateEstimatedNetworkBytes(JobParameters, long, long)} or
+ * {@link JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)}
+ * shortly after the job starts.
+ *
+ * <p>
+ * For user-initiated transfers that must be started immediately, call
+ * {@link #setExpedited(boolean) setExpedited(true)}. Otherwise, the system may defer the
+ * job to a more opportune time. Using {@link #setExpedited(boolean) setExpedited(true)}
+ * with this API will only be allowed for foreground apps and when the user has clearly
+ * interacted with the app. {@link #setExpedited(boolean) setExpedited(true)} will return
+ * {@link JobScheduler#RESULT_FAILURE} for a data transfer job if the app is in the
+ * background. Apps that successfully schedule data transfer jobs with
+ * {@link #setExpedited(boolean) setExpedited(true)} will not have quotas applied to them,
+ * though they may still be stopped for system health or constraint reasons. The system will
+ * also give a user the ability to stop a data transfer job via the Task Manager.
+ *
+ * <p>
+ * If you want to perform more than one data transfer job, consider enqueuing multiple
+ * {@link JobWorkItem JobWorkItems} along with {@link #setDataTransfer(boolean)}.
+ *
+ * @see JobInfo#isDataTransfer()
+ * @hide
+ */
+ @NonNull
+ public Builder setDataTransfer(boolean dataTransfer) {
+ if (dataTransfer) {
+ mFlags |= FLAG_DATA_TRANSFER;
+ } else {
+ mFlags &= (~FLAG_DATA_TRANSFER);
+ }
+ return this;
+ }
+
+ /**
* Setting this to true indicates that this job is important while the scheduling app
* is in the foreground or on the temporary whitelist for background restrictions.
* This means that the system will relax doze restrictions on this job during this time.
@@ -2062,8 +2123,9 @@
"An expedited job must be high or max priority. Don't use expedited jobs"
+ " for unimportant tasks.");
}
- if ((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
- || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY)) != 0) {
+ if (((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
+ || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY
+ | FLAG_DATA_TRANSFER)) != 0)) {
throw new IllegalArgumentException(
"An expedited job can only have network and storage-not-low constraints");
}
@@ -2072,6 +2134,24 @@
"Can't call addTriggerContentUri() on an expedited job");
}
}
+
+ if ((flags & FLAG_DATA_TRANSFER) != 0) {
+ if (backoffPolicy == BACKOFF_POLICY_LINEAR) {
+ throw new IllegalArgumentException(
+ "A data transfer job cannot have a linear backoff policy.");
+ }
+ if (hasLateConstraint) {
+ throw new IllegalArgumentException("A data transfer job cannot have a deadline");
+ }
+ if ((flags & FLAG_PREFETCH) != 0) {
+ throw new IllegalArgumentException(
+ "A data transfer job cannot also be a prefetch job");
+ }
+ if (networkRequest == null) {
+ throw new IllegalArgumentException(
+ "A data transfer job must specify a valid network type");
+ }
+ }
}
/**
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index dfdb290..76f71a2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -22,8 +22,12 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.UserIdInt;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.ClipData;
import android.content.Context;
+import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -93,6 +97,16 @@
*/
@SystemService(Context.JOB_SCHEDULER_SERVICE)
public abstract class JobScheduler {
+ /**
+ * Whether to throw an exception when an app doesn't properly implement all the necessary
+ * data transfer APIs.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION = 255371817L;
+
/** @hide */
@IntDef(prefix = { "RESULT_" }, value = {
RESULT_FAILURE,
@@ -236,6 +250,23 @@
public abstract @Nullable JobInfo getPendingJob(int jobId);
/**
+ * Returns {@code true} if the calling app currently holds the
+ * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
+ */
+ public boolean canRunLongJobs() {
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if the app currently holds the
+ * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
+ * @hide
+ */
+ public boolean hasRunLongJobsPermission(@NonNull String packageName, @UserIdInt int userId) {
+ return false;
+ }
+
+ /**
* <b>For internal system callers only!</b>
* Returns a list of all currently-executing jobs.
* @hide
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
index 7b287d5..f56e1ee 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
@@ -44,9 +44,9 @@
* <p>If this is called from other places, it throws a {@link IllegalStateException).
*/
public static void registerServiceWrappers() {
- SystemServiceRegistry.registerStaticService(
+ SystemServiceRegistry.registerContextAwareService(
Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
- (b) -> new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b)));
+ (context, b) -> new JobSchedulerImpl(context, IJobScheduler.Stub.asInterface(b)));
SystemServiceRegistry.registerContextAwareService(
Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class,
(context, b) -> new DeviceIdleManager(
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index d184d44..dabf728 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -16,7 +16,13 @@
package android.app.job;
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
+import android.compat.Compatibility;
import android.content.Intent;
import android.os.IBinder;
@@ -72,6 +78,28 @@
public boolean onStopJob(JobParameters params) {
return JobService.this.onStopJob(params);
}
+
+ @Override
+ @BytesLong
+ public long getTransferredDownloadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (item == null) {
+ return JobService.this.getTransferredDownloadBytes();
+ } else {
+ return JobService.this.getTransferredDownloadBytes(item);
+ }
+ }
+
+ @Override
+ @BytesLong
+ public long getTransferredUploadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (item == null) {
+ return JobService.this.getTransferredUploadBytes();
+ } else {
+ return JobService.this.getTransferredUploadBytes(item);
+ }
+ }
};
}
return mEngine.getBinder();
@@ -171,4 +199,161 @@
* to end the job entirely. Regardless of the value returned, your job must stop executing.
*/
public abstract boolean onStopJob(JobParameters params);
+
+ /**
+ * Update how much data this job will transfer. This method can
+ * be called multiple times within the first 30 seconds after
+ * {@link #onStartJob(JobParameters)} has been called. Only
+ * one call will be heeded after that time has passed.
+ *
+ * This method (or an overload) must be called within the first
+ * 30 seconds for a data transfer job if a payload size estimate
+ * was not provided at the time of scheduling.
+ *
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ */
+ public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+ @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+ mEngine.updateEstimatedNetworkBytes(params, null, downloadBytes, uploadBytes);
+ }
+
+ /**
+ * Update how much data will transfer for the JobWorkItem. This
+ * method can be called multiple times within the first 30 seconds
+ * after {@link #onStartJob(JobParameters)} has been called.
+ * Only one call will be heeded after that time has passed.
+ *
+ * This method (or an overload) must be called within the first
+ * 30 seconds for a data transfer job if a payload size estimate
+ * was not provided at the time of scheduling.
+ *
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ */
+ public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem jobWorkItem,
+ @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+ mEngine.updateEstimatedNetworkBytes(params, jobWorkItem, downloadBytes, uploadBytes);
+ }
+
+ /**
+ * Tell JobScheduler how much data has successfully been transferred for the data transfer job.
+ */
+ public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+ @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+ mEngine.updateTransferredNetworkBytes(params, null,
+ transferredDownloadBytes, transferredUploadBytes);
+ }
+
+ /**
+ * Tell JobScheduler how much data has been transferred for the data transfer
+ * {@link JobWorkItem}.
+ */
+ public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item,
+ @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+ mEngine.updateTransferredNetworkBytes(params, item,
+ transferredDownloadBytes, transferredUploadBytes);
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated download bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+ * hasn't been called recently.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredDownloadBytes() {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated upload bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+ * hasn't been called recently.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredUploadBytes() {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated download bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+ * hasn't been called recently and the job has
+ * {@link JobWorkItem JobWorkItems} that have been
+ * {@link JobParameters#dequeueWork dequeued} but not
+ * {@link JobParameters#completeWork(JobWorkItem) completed}.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
+ if (item == null) {
+ return getTransferredDownloadBytes();
+ }
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated upload bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+ * hasn't been called recently and the job has
+ * {@link JobWorkItem JobWorkItems} that have been
+ * {@link JobParameters#dequeueWork dequeued} but not
+ * {@link JobParameters#completeWork(JobWorkItem) completed}.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
+ if (item == null) {
+ return getTransferredUploadBytes();
+ }
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 3d43d20..6c4b686 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -16,7 +16,13 @@
package android.app.job;
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
+import android.compat.Compatibility;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
@@ -25,6 +31,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.os.SomeArgs;
+
import java.lang.ref.WeakReference;
/**
@@ -51,6 +59,20 @@
* Message that the client has completed execution of this job.
*/
private static final int MSG_JOB_FINISHED = 2;
+ /**
+ * Message that will result in a call to
+ * {@link #getTransferredDownloadBytes(JobParameters, JobWorkItem)}.
+ */
+ private static final int MSG_GET_TRANSFERRED_DOWNLOAD_BYTES = 3;
+ /**
+ * Message that will result in a call to
+ * {@link #getTransferredUploadBytes(JobParameters, JobWorkItem)}.
+ */
+ private static final int MSG_GET_TRANSFERRED_UPLOAD_BYTES = 4;
+ /** Message that the client wants to update JobScheduler of the data transfer progress. */
+ private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
+ /** Message that the client wants to update JobScheduler of the estimated transfer size. */
+ private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
private final IJobService mBinder;
@@ -68,6 +90,32 @@
}
@Override
+ public void getTransferredDownloadBytes(@NonNull JobParameters jobParams,
+ @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+ JobServiceEngine service = mService.get();
+ if (service != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = jobParams;
+ args.arg2 = jobWorkItem;
+ service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_DOWNLOAD_BYTES, args)
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void getTransferredUploadBytes(@NonNull JobParameters jobParams,
+ @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+ JobServiceEngine service = mService.get();
+ if (service != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = jobParams;
+ args.arg2 = jobWorkItem;
+ service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_UPLOAD_BYTES, args)
+ .sendToTarget();
+ }
+ }
+
+ @Override
public void startJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
@@ -98,9 +146,9 @@
@Override
public void handleMessage(Message msg) {
- final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
- case MSG_EXECUTE_JOB:
+ case MSG_EXECUTE_JOB: {
+ final JobParameters params = (JobParameters) msg.obj;
try {
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
ackStartMessage(params, workOngoing);
@@ -109,7 +157,9 @@
throw new RuntimeException(e);
}
break;
- case MSG_STOP_JOB:
+ }
+ case MSG_STOP_JOB: {
+ final JobParameters params = (JobParameters) msg.obj;
try {
boolean ret = JobServiceEngine.this.onStopJob(params);
ackStopMessage(params, ret);
@@ -118,7 +168,9 @@
throw new RuntimeException(e);
}
break;
- case MSG_JOB_FINISHED:
+ }
+ case MSG_JOB_FINISHED: {
+ final JobParameters params = (JobParameters) msg.obj;
final boolean needsReschedule = (msg.arg2 == 1);
IJobCallback callback = params.getCallback();
if (callback != null) {
@@ -132,19 +184,117 @@
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
+ }
+ case MSG_GET_TRANSFERRED_DOWNLOAD_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ final JobWorkItem item = (JobWorkItem) args.arg2;
+ try {
+ long ret = JobServiceEngine.this.getTransferredDownloadBytes(params, item);
+ ackGetTransferredDownloadBytesMessage(params, item, ret);
+ } catch (Exception e) {
+ Log.e(TAG, "Application unable to handle getTransferredDownloadBytes.", e);
+ throw new RuntimeException(e);
+ }
+ args.recycle();
+ break;
+ }
+ case MSG_GET_TRANSFERRED_UPLOAD_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ final JobWorkItem item = (JobWorkItem) args.arg2;
+ try {
+ long ret = JobServiceEngine.this.getTransferredUploadBytes(params, item);
+ ackGetTransferredUploadBytesMessage(params, item, ret);
+ } catch (Exception e) {
+ Log.e(TAG, "Application unable to handle getTransferredUploadBytes.", e);
+ throw new RuntimeException(e);
+ }
+ args.recycle();
+ break;
+ }
+ case MSG_UPDATE_TRANSFERRED_NETWORK_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ IJobCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.updateTransferredNetworkBytes(params.getJobId(),
+ (JobWorkItem) args.arg2, args.argl1, args.argl2);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating data transfer progress to system:"
+ + " binder has gone away.");
+ }
+ } else {
+ Log.e(TAG, "updateDataTransferProgress() called for a nonexistent job id.");
+ }
+ args.recycle();
+ break;
+ }
+ case MSG_UPDATE_ESTIMATED_NETWORK_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ IJobCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.updateEstimatedNetworkBytes(params.getJobId(),
+ (JobWorkItem) args.arg2, args.argl1, args.argl2);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating estimated transfer size to system:"
+ + " binder has gone away.");
+ }
+ } else {
+ Log.e(TAG,
+ "updateEstimatedNetworkBytes() called for a nonexistent job id.");
+ }
+ args.recycle();
+ break;
+ }
default:
Log.e(TAG, "Unrecognised message received.");
break;
}
}
+ private void ackGetTransferredDownloadBytesMessage(@NonNull JobParameters params,
+ @Nullable JobWorkItem item, long progress) {
+ final IJobCallback callback = params.getCallback();
+ final int jobId = params.getJobId();
+ final int workId = item == null ? -1 : item.getWorkId();
+ if (callback != null) {
+ try {
+ callback.acknowledgeGetTransferredDownloadBytesMessage(jobId, workId, progress);
+ } catch (RemoteException e) {
+ Log.e(TAG, "System unreachable for returning progress.");
+ }
+ } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Attempting to ack a job that has already been processed.");
+ }
+ }
+
+ private void ackGetTransferredUploadBytesMessage(@NonNull JobParameters params,
+ @Nullable JobWorkItem item, long progress) {
+ final IJobCallback callback = params.getCallback();
+ final int jobId = params.getJobId();
+ final int workId = item == null ? -1 : item.getWorkId();
+ if (callback != null) {
+ try {
+ callback.acknowledgeGetTransferredUploadBytesMessage(jobId, workId, progress);
+ } catch (RemoteException e) {
+ Log.e(TAG, "System unreachable for returning progress.");
+ }
+ } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Attempting to ack a job that has already been processed.");
+ }
+ }
+
private void ackStartMessage(JobParameters params, boolean workOngoing) {
final IJobCallback callback = params.getCallback();
final int jobId = params.getJobId();
if (callback != null) {
try {
callback.acknowledgeStartMessage(jobId, workOngoing);
- } catch(RemoteException e) {
+ } catch (RemoteException e) {
Log.e(TAG, "System unreachable for starting job.");
}
} else {
@@ -213,4 +363,69 @@
m.arg2 = needsReschedule ? 1 : 0;
m.sendToTarget();
}
+
+ /**
+ * Engine's request to get how much data has been downloaded.
+ *
+ * @see JobService#getTransferredDownloadBytes()
+ */
+ @BytesLong
+ public long getTransferredDownloadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Engine's request to get how much data has been uploaded.
+ *
+ * @see JobService#getTransferredUploadBytes()
+ */
+ @BytesLong
+ public long getTransferredUploadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Call in to engine to report data transfer progress.
+ *
+ * @see JobService#updateTransferredNetworkBytes(JobParameters, long, long)
+ */
+ public void updateTransferredNetworkBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ if (params == null) {
+ throw new NullPointerException("params");
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = params;
+ args.arg2 = item;
+ args.argl1 = downloadBytes;
+ args.argl2 = uploadBytes;
+ mHandler.obtainMessage(MSG_UPDATE_TRANSFERRED_NETWORK_BYTES, args).sendToTarget();
+ }
+
+ /**
+ * Call in to engine to report data transfer progress.
+ *
+ * @see JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)
+ */
+ public void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item,
+ @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+ if (params == null) {
+ throw new NullPointerException("params");
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = params;
+ args.arg2 = item;
+ args.argl1 = downloadBytes;
+ args.argl2 = uploadBytes;
+ mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
+ }
}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index bd475e9..e3bd5ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -31,6 +31,7 @@
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
@@ -740,8 +741,10 @@
}
};
- private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
- @Override public void onReceive(Context context, Intent intent) {
+ private final IIntentReceiver mIdleStartedDoneReceiver = new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+ boolean ordered, boolean sticky, int sendingUser) {
// When coming out of a deep idle, we will add in some delay before we allow
// the system to settle down and finish the maintenance window. This is
// to give a chance for any pending work to be scheduled.
@@ -1816,13 +1819,15 @@
}
if (deepChanged) {
incActiveIdleOps();
- getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
- null, mIdleStartedDoneReceiver, null, 0, null, null);
+ mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent,
+ mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
+ null, null, null);
}
if (lightChanged) {
incActiveIdleOps();
- getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
- null, mIdleStartedDoneReceiver, null, 0, null, null);
+ mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent,
+ mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
+ null, null, null);
}
// Always start with one active op for the message being sent here.
// Now we are done!
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index bedfa7f..29e730d 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -84,6 +84,7 @@
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserPackage;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
@@ -120,7 +121,6 @@
import android.util.IntArray;
import android.util.Log;
import android.util.LongArrayQueue;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
@@ -402,7 +402,7 @@
public long lastUsage;
}
/** Map of {package, user} -> {quotaInfo} */
- private final ArrayMap<Pair<String, Integer>, QuotaInfo> mQuotaBuffer = new ArrayMap<>();
+ private final ArrayMap<UserPackage, QuotaInfo> mQuotaBuffer = new ArrayMap<>();
private long mMaxDuration;
@@ -414,11 +414,11 @@
if (quota <= 0) {
return;
}
- final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
- QuotaInfo currentQuotaInfo = mQuotaBuffer.get(packageUser);
+ final UserPackage userPackage = UserPackage.of(userId, packageName);
+ QuotaInfo currentQuotaInfo = mQuotaBuffer.get(userPackage);
if (currentQuotaInfo == null) {
currentQuotaInfo = new QuotaInfo();
- mQuotaBuffer.put(packageUser, currentQuotaInfo);
+ mQuotaBuffer.put(userPackage, currentQuotaInfo);
}
currentQuotaInfo.remainingQuota = quota;
currentQuotaInfo.expirationTime = nowElapsed + mMaxDuration;
@@ -426,8 +426,8 @@
/** Returns if the supplied package has reserve quota to fire at the given time. */
boolean hasQuota(String packageName, int userId, long triggerElapsed) {
- final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
- final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
+ final UserPackage userPackage = UserPackage.of(userId, packageName);
+ final QuotaInfo quotaInfo = mQuotaBuffer.get(userPackage);
return quotaInfo != null && quotaInfo.remainingQuota > 0
&& triggerElapsed <= quotaInfo.expirationTime;
@@ -438,8 +438,8 @@
* required.
*/
void recordUsage(String packageName, int userId, long nowElapsed) {
- final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
- final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
+ final UserPackage userPackage = UserPackage.of(userId, packageName);
+ final QuotaInfo quotaInfo = mQuotaBuffer.get(userPackage);
if (quotaInfo == null) {
Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
@@ -479,26 +479,26 @@
void removeForUser(int userId) {
for (int i = mQuotaBuffer.size() - 1; i >= 0; i--) {
- final Pair<String, Integer> packageUserKey = mQuotaBuffer.keyAt(i);
- if (packageUserKey.second == userId) {
+ final UserPackage userPackageKey = mQuotaBuffer.keyAt(i);
+ if (userPackageKey.userId == userId) {
mQuotaBuffer.removeAt(i);
}
}
}
void removeForPackage(String packageName, int userId) {
- final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
- mQuotaBuffer.remove(packageUser);
+ final UserPackage userPackage = UserPackage.of(userId, packageName);
+ mQuotaBuffer.remove(userPackage);
}
void dump(IndentingPrintWriter pw, long nowElapsed) {
pw.increaseIndent();
for (int i = 0; i < mQuotaBuffer.size(); i++) {
- final Pair<String, Integer> packageUser = mQuotaBuffer.keyAt(i);
+ final UserPackage userPackage = mQuotaBuffer.keyAt(i);
final QuotaInfo quotaInfo = mQuotaBuffer.valueAt(i);
- pw.print(packageUser.first);
+ pw.print(userPackage.packageName);
pw.print(", u");
- pw.print(packageUser.second);
+ pw.print(userPackage.userId);
pw.print(": ");
if (quotaInfo == null) {
pw.print("--");
@@ -522,8 +522,7 @@
*/
@VisibleForTesting
static class AppWakeupHistory {
- private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
- new ArrayMap<>();
+ private final ArrayMap<UserPackage, LongArrayQueue> mPackageHistory = new ArrayMap<>();
private long mWindowSize;
AppWakeupHistory(long windowSize) {
@@ -531,11 +530,11 @@
}
void recordAlarmForPackage(String packageName, int userId, long nowElapsed) {
- final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
- LongArrayQueue history = mPackageHistory.get(packageUser);
+ final UserPackage userPackage = UserPackage.of(userId, packageName);
+ LongArrayQueue history = mPackageHistory.get(userPackage);
if (history == null) {
history = new LongArrayQueue();
- mPackageHistory.put(packageUser, history);
+ mPackageHistory.put(userPackage, history);
}
if (history.size() == 0 || history.peekLast() < nowElapsed) {
history.addLast(nowElapsed);
@@ -545,16 +544,16 @@
void removeForUser(int userId) {
for (int i = mPackageHistory.size() - 1; i >= 0; i--) {
- final Pair<String, Integer> packageUserKey = mPackageHistory.keyAt(i);
- if (packageUserKey.second == userId) {
+ final UserPackage userPackageKey = mPackageHistory.keyAt(i);
+ if (userPackageKey.userId == userId) {
mPackageHistory.removeAt(i);
}
}
}
void removeForPackage(String packageName, int userId) {
- final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
- mPackageHistory.remove(packageUser);
+ final UserPackage userPackage = UserPackage.of(userId, packageName);
+ mPackageHistory.remove(userPackage);
}
private void snapToWindow(LongArrayQueue history) {
@@ -564,7 +563,7 @@
}
int getTotalWakeupsInWindow(String packageName, int userId) {
- final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+ final LongArrayQueue history = mPackageHistory.get(UserPackage.of(userId, packageName));
return (history == null) ? 0 : history.size();
}
@@ -573,7 +572,7 @@
* (1=1st-last=the ultimate wakeup and 2=2nd-last=the penultimate wakeup)
*/
long getNthLastWakeupForPackage(String packageName, int userId, int n) {
- final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+ final LongArrayQueue history = mPackageHistory.get(UserPackage.of(userId, packageName));
if (history == null) {
return 0;
}
@@ -584,11 +583,11 @@
void dump(IndentingPrintWriter pw, long nowElapsed) {
pw.increaseIndent();
for (int i = 0; i < mPackageHistory.size(); i++) {
- final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
+ final UserPackage userPackage = mPackageHistory.keyAt(i);
final LongArrayQueue timestamps = mPackageHistory.valueAt(i);
- pw.print(packageUser.first);
+ pw.print(userPackage.packageName);
pw.print(", u");
- pw.print(packageUser.second);
+ pw.print(userPackage.userId);
pw.print(": ");
// limit dumping to a max of 100 values
final int lastIdx = Math.max(0, timestamps.size() - 100);
@@ -1501,13 +1500,13 @@
* null indicates all
* @return True if there was any reordering done to the current list.
*/
- boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) {
+ boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<UserPackage> targetPackages) {
final long start = mStatLogger.getTime();
final boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
- final Pair<String, Integer> packageUser =
- Pair.create(a.sourcePackage, UserHandle.getUserId(a.creatorUid));
- if (targetPackages != null && !targetPackages.contains(packageUser)) {
+ final UserPackage userPackage =
+ UserPackage.of(UserHandle.getUserId(a.creatorUid), a.sourcePackage);
+ if (targetPackages != null && !targetPackages.contains(userPackage)) {
return false;
}
return adjustDeliveryTimeBasedOnBucketLocked(a);
@@ -1524,13 +1523,13 @@
* null indicates all
* @return True if there was any reordering done to the current list.
*/
- boolean reorderAlarmsBasedOnTare(ArraySet<Pair<String, Integer>> targetPackages) {
+ boolean reorderAlarmsBasedOnTare(ArraySet<UserPackage> targetPackages) {
final long start = mStatLogger.getTime();
final boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
- final Pair<String, Integer> packageUser =
- Pair.create(a.sourcePackage, UserHandle.getUserId(a.creatorUid));
- if (targetPackages != null && !targetPackages.contains(packageUser)) {
+ final UserPackage userPackage =
+ UserPackage.of(UserHandle.getUserId(a.creatorUid), a.sourcePackage);
+ if (targetPackages != null && !targetPackages.contains(userPackage)) {
return false;
}
return adjustDeliveryTimeBasedOnTareLocked(a);
@@ -4786,8 +4785,7 @@
}
}
}
- final ArraySet<Pair<String, Integer>> triggerPackages =
- new ArraySet<>();
+ final ArraySet<UserPackage> triggerPackages = new ArraySet<>();
final IntArray wakeupUids = new IntArray();
for (int i = 0; i < triggerList.size(); i++) {
final Alarm a = triggerList.get(i);
@@ -4796,13 +4794,13 @@
}
if (mConstants.USE_TARE_POLICY) {
if (!isExemptFromTare(a)) {
- triggerPackages.add(Pair.create(
- a.sourcePackage,
- UserHandle.getUserId(a.creatorUid)));
+ triggerPackages.add(UserPackage.of(
+ UserHandle.getUserId(a.creatorUid),
+ a.sourcePackage));
}
} else if (!isExemptFromAppStandby(a)) {
- triggerPackages.add(Pair.create(
- a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
+ triggerPackages.add(UserPackage.of(
+ UserHandle.getUserId(a.creatorUid), a.sourcePackage));
}
}
if (wakeupUids.size() > 0 && mBatteryStatsInternal != null) {
@@ -4990,8 +4988,8 @@
case TEMPORARY_QUOTA_CHANGED:
case APP_STANDBY_BUCKET_CHANGED:
synchronized (mLock) {
- final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
- filterPackages.add(Pair.create((String) msg.obj, msg.arg1));
+ final ArraySet<UserPackage> filterPackages = new ArraySet<>();
+ filterPackages.add(UserPackage.of(msg.arg1, (String) msg.obj));
if (reorderAlarmsBasedOnStandbyBuckets(filterPackages)) {
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
@@ -5004,8 +5002,8 @@
final int userId = msg.arg1;
final String packageName = (String) msg.obj;
- final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
- filterPackages.add(Pair.create(packageName, userId));
+ final ArraySet<UserPackage> filterPackages = new ArraySet<>();
+ filterPackages.add(UserPackage.of(userId, packageName));
if (reorderAlarmsBasedOnTare(filterPackages)) {
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 52dc01b..f9dd0b3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,8 @@
package com.android.server.job;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
@@ -99,6 +101,18 @@
static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = STANDARD_CONCURRENCY_LIMIT / 2;
+ @VisibleForTesting
+ static final String KEY_ENABLE_MAX_WAIT_TIME_BYPASS =
+ CONFIG_KEY_PREFIX_CONCURRENCY + "enable_max_wait_time_bypass";
+ private static final boolean DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS = true;
+ private static final String KEY_MAX_WAIT_EJ_MS =
+ CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_ej_ms";
+ @VisibleForTesting
+ static final long DEFAULT_MAX_WAIT_EJ_MS = 5 * MINUTE_IN_MILLIS;
+ private static final String KEY_MAX_WAIT_REGULAR_MS =
+ CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_regular_ms";
+ @VisibleForTesting
+ static final long DEFAULT_MAX_WAIT_REGULAR_MS = 30 * MINUTE_IN_MILLIS;
/**
* Set of possible execution types that a job can have. The actual type(s) of a job are based
@@ -313,6 +327,7 @@
private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
+ private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();
private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
@@ -353,6 +368,20 @@
*/
private int mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
+ private boolean mMaxWaitTimeBypassEnabled = DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS;
+
+ /**
+ * The maximum time an expedited job would have to be potentially waiting for an available
+ * slot before we would consider creating a new slot for it.
+ */
+ private long mMaxWaitEjMs = DEFAULT_MAX_WAIT_EJ_MS;
+
+ /**
+ * The maximum time a regular job would have to be potentially waiting for an available
+ * slot before we would consider creating a new slot for it.
+ */
+ private long mMaxWaitRegularMs = DEFAULT_MAX_WAIT_REGULAR_MS;
+
/** Current memory trim level. */
private int mLastMemoryTrimLevel;
@@ -386,7 +415,7 @@
@VisibleForTesting
JobConcurrencyManager(JobSchedulerService service, Injector injector) {
mService = service;
- mLock = mService.mLock;
+ mLock = mService.getLock();
mContext = service.getTestableContext();
mInjector = injector;
@@ -666,7 +695,8 @@
}
prepareForAssignmentDeterminationLocked(
- mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+ mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+ mRecycledAssignmentInfo);
if (DEBUG) {
Slog.d(TAG, printAssignments("running jobs initial",
@@ -674,7 +704,8 @@
}
determineAssignmentsLocked(
- mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+ mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+ mRecycledAssignmentInfo);
if (DEBUG) {
Slog.d(TAG, printAssignments("running jobs final",
@@ -686,7 +717,8 @@
carryOutAssignmentChangesLocked(mRecycledChanged);
cleanUpAfterAssignmentChangesLocked(
- mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+ mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+ mRecycledAssignmentInfo);
noteConcurrency();
}
@@ -695,7 +727,8 @@
@GuardedBy("mLock")
void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
final List<ContextAssignment> preferredUidOnly,
- final List<ContextAssignment> stoppable) {
+ final List<ContextAssignment> stoppable,
+ final AssignmentInfo info) {
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
final List<JobServiceContext> activeServices = mActiveServices;
@@ -709,6 +742,8 @@
updateNonRunningPrioritiesLocked(pendingJobQueue, true);
final int numRunningJobs = activeServices.size();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ long minPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
for (int i = 0; i < numRunningJobs; ++i) {
final JobServiceContext jsc = activeServices.get(i);
final JobStatus js = jsc.getRunningJobLocked();
@@ -723,12 +758,18 @@
if (js != null) {
mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
assignment.workType = jsc.getRunningJobWorkType();
+ if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+ info.numRunningTopEj++;
+ }
}
assignment.preferredUid = jsc.getPreferredUid();
if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
stoppable.add(assignment);
} else {
+ assignment.timeUntilStoppableMs = jsc.getRemainingGuaranteedTimeMs(nowElapsed);
+ minPreferredUidOnlyWaitingTimeMs =
+ Math.min(minPreferredUidOnlyWaitingTimeMs, assignment.timeUntilStoppableMs);
preferredUidOnly.add(assignment);
}
}
@@ -754,6 +795,11 @@
}
mWorkCountTracker.onCountDone();
+ // Set 0 if there were no preferred UID only contexts to indicate no waiting time due
+ // to such jobs.
+ info.minPreferredUidOnlyWaitingTimeMs =
+ minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
+ ? 0 : minPreferredUidOnlyWaitingTimeMs;
}
@VisibleForTesting
@@ -761,12 +807,14 @@
void determineAssignmentsLocked(final ArraySet<ContextAssignment> changed,
final ArraySet<ContextAssignment> idle,
final List<ContextAssignment> preferredUidOnly,
- final List<ContextAssignment> stoppable) {
+ final List<ContextAssignment> stoppable,
+ @NonNull AssignmentInfo info) {
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
final List<JobServiceContext> activeServices = mActiveServices;
pendingJobQueue.resetIterator();
JobStatus nextPending;
int projectedRunningCount = activeServices.size();
+ long minChangedWaitingTimeMs = Long.MAX_VALUE;
while ((nextPending = pendingJobQueue.next()) != null) {
if (mRunningJobs.contains(nextPending)) {
// Should never happen.
@@ -785,6 +833,14 @@
+ " to: " + nextPending);
}
+ // Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
+ // the number of additional contexts that are created due to long waiting times.
+ // By factoring it in, we imply that the new slot will be available for other
+ // pending jobs that could be designated as waiting too long, and those other jobs
+ // would only have to wait for the new slots to become available.
+ final long minWaitingTimeMs =
+ Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
+
// Find an available slot for nextPending. The context should be one of the following:
// 1. Unused
// 2. Its job should have used up its minimum execution guarantee so it
@@ -812,13 +868,6 @@
}
}
if (selectedContext == null && stoppable.size() > 0) {
- int topEjCount = 0;
- for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
- JobStatus js = mRunningJobs.valueAt(r);
- if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- topEjCount++;
- }
- }
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -833,12 +882,21 @@
// app was on TOP, the app is still TOP, but there are too many TOP+EJs
// running (because we don't want them to starve out other apps and the
// current job has already run for the minimum guaranteed time).
+ // 5. This new job could be waiting for too long for a slot to open up
boolean canReplace = isTopEj; // Case 1
if (!canReplace && !isInOverage) {
final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
|| currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
- || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
+ // Case 4
+ || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
+ }
+ if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
+ if (nextPending.shouldTreatAsExpeditedJob()) {
+ canReplace = minWaitingTimeMs >= mMaxWaitEjMs;
+ } else {
+ canReplace = minWaitingTimeMs >= mMaxWaitRegularMs;
+ }
}
if (canReplace) {
int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
@@ -860,6 +918,7 @@
}
if (selectedContext == null && (!isInOverage || isTopEj)) {
int lowestBiasSeen = Integer.MAX_VALUE;
+ long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
final ContextAssignment assignment = preferredUidOnly.get(p);
final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -872,6 +931,13 @@
}
if (selectedContext == null || lowestBiasSeen > jobBias) {
+ if (selectedContext != null) {
+ // We're no longer using the previous context, so factor it into the
+ // calculation.
+ newMinPreferredUidOnlyWaitingTimeMs = Math.min(
+ newMinPreferredUidOnlyWaitingTimeMs,
+ selectedContext.timeUntilStoppableMs);
+ }
// Step down the preemption threshold - wind up replacing
// the lowest-bias running job
lowestBiasSeen = jobBias;
@@ -880,11 +946,17 @@
assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
// In this case, we're just going to preempt a low bias job, we're not
// actually starting a job, so don't set startingJob to true.
+ } else {
+ // We're not going to use this context, so factor it into the calculation.
+ newMinPreferredUidOnlyWaitingTimeMs = Math.min(
+ newMinPreferredUidOnlyWaitingTimeMs,
+ assignment.timeUntilStoppableMs);
}
}
if (selectedContext != null) {
selectedContext.newJob = nextPending;
preferredUidOnly.remove(selectedContext);
+ info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
}
}
// Make sure to run EJs for the TOP app immediately.
@@ -901,6 +973,9 @@
selectedContext = null;
}
if (selectedContext == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Allowing additional context because EJ would wait too long");
+ }
selectedContext = mContextAssignmentPool.acquire();
if (selectedContext == null) {
selectedContext = new ContextAssignment();
@@ -913,6 +988,35 @@
selectedContext.newWorkType =
(workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP;
}
+ } else if (selectedContext == null && mMaxWaitTimeBypassEnabled) {
+ final boolean wouldBeWaitingTooLong = nextPending.shouldTreatAsExpeditedJob()
+ ? minWaitingTimeMs >= mMaxWaitEjMs
+ : minWaitingTimeMs >= mMaxWaitRegularMs;
+ if (wouldBeWaitingTooLong) {
+ if (DEBUG) {
+ Slog.d(TAG, "Allowing additional context because job would wait too long");
+ }
+ selectedContext = mContextAssignmentPool.acquire();
+ if (selectedContext == null) {
+ selectedContext = new ContextAssignment();
+ }
+ selectedContext.context = mIdleContexts.size() > 0
+ ? mIdleContexts.removeAt(mIdleContexts.size() - 1)
+ : createNewJobServiceContext();
+ selectedContext.newJob = nextPending;
+ final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
+ if (workType != WORK_TYPE_NONE) {
+ selectedContext.newWorkType = workType;
+ } else {
+ // Use the strongest work type possible for this job.
+ for (int type = 1; type <= ALL_WORK_TYPES; type = type << 1) {
+ if ((type & allWorkTypes) != 0) {
+ selectedContext.newWorkType = type;
+ break;
+ }
+ }
+ }
+ }
}
final PackageStats packageStats = getPkgStatsLocked(
nextPending.getSourceUserId(), nextPending.getSourcePackageName());
@@ -923,6 +1027,8 @@
}
if (selectedContext.newJob != null) {
projectedRunningCount++;
+ minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
+ mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
}
packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
}
@@ -967,7 +1073,8 @@
private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
final ArraySet<ContextAssignment> idle,
final List<ContextAssignment> preferredUidOnly,
- final List<ContextAssignment> stoppable) {
+ final List<ContextAssignment> stoppable,
+ final AssignmentInfo assignmentInfo) {
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
assignment.clear();
@@ -988,6 +1095,7 @@
idle.clear();
stoppable.clear();
preferredUidOnly.clear();
+ assignmentInfo.clear();
mWorkCountTracker.resetStagingCount();
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
}
@@ -1251,13 +1359,37 @@
}
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
- if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT || pendingJobQueue.size() == 0) {
+ if (pendingJobQueue.size() == 0) {
worker.clearPreferredUid();
- // We're over the limit (because the TOP app scheduled a lot of EJs). Don't start
- // running anything new until we get back below the limit.
noteConcurrency();
return;
}
+ if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT) {
+ final boolean respectConcurrencyLimit;
+ if (!mMaxWaitTimeBypassEnabled) {
+ respectConcurrencyLimit = true;
+ } else {
+ long minWaitingTimeMs = Long.MAX_VALUE;
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ for (int i = mActiveServices.size() - 1; i >= 0; --i) {
+ minWaitingTimeMs = Math.min(minWaitingTimeMs,
+ mActiveServices.get(i).getRemainingGuaranteedTimeMs(nowElapsed));
+ }
+ final boolean wouldBeWaitingTooLong =
+ mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0
+ ? minWaitingTimeMs >= mMaxWaitEjMs
+ : minWaitingTimeMs >= mMaxWaitRegularMs;
+ respectConcurrencyLimit = !wouldBeWaitingTooLong;
+ }
+ if (respectConcurrencyLimit) {
+ worker.clearPreferredUid();
+ // We're over the limit (because the TOP app scheduled a lot of EJs), but we should
+ // be able to stop the other jobs soon so don't start running anything new until we
+ // get back below the limit.
+ noteConcurrency();
+ return;
+ }
+ }
if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
updateCounterConfigLocked();
@@ -1609,6 +1741,14 @@
mPkgConcurrencyLimitRegular = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT,
properties.getInt(
KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
+
+ mMaxWaitTimeBypassEnabled = properties.getBoolean(
+ KEY_ENABLE_MAX_WAIT_TIME_BYPASS, DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS);
+ // EJ max wait must be in the range [0, infinity).
+ mMaxWaitEjMs = Math.max(0, properties.getLong(KEY_MAX_WAIT_EJ_MS, DEFAULT_MAX_WAIT_EJ_MS));
+ // Regular max wait must be in the range [EJ max wait, infinity).
+ mMaxWaitRegularMs = Math.max(mMaxWaitEjMs,
+ properties.getLong(KEY_MAX_WAIT_REGULAR_MS, DEFAULT_MAX_WAIT_REGULAR_MS));
}
@GuardedBy("mLock")
@@ -1622,6 +1762,9 @@
pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
+ pw.print(KEY_ENABLE_MAX_WAIT_TIME_BYPASS, mMaxWaitTimeBypassEnabled).println();
+ pw.print(KEY_MAX_WAIT_EJ_MS, mMaxWaitEjMs).println();
+ pw.print(KEY_MAX_WAIT_REGULAR_MS, mMaxWaitRegularMs).println();
pw.println();
CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
pw.println();
@@ -2382,6 +2525,7 @@
public int workType = WORK_TYPE_NONE;
public String preemptReason;
public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
+ public long timeUntilStoppableMs;
public String shouldStopJobReason;
public JobStatus newJob;
public int newWorkType = WORK_TYPE_NONE;
@@ -2392,12 +2536,24 @@
workType = WORK_TYPE_NONE;
preemptReason = null;
preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
+ timeUntilStoppableMs = 0;
shouldStopJobReason = null;
newJob = null;
newWorkType = WORK_TYPE_NONE;
}
}
+ @VisibleForTesting
+ static final class AssignmentInfo {
+ public long minPreferredUidOnlyWaitingTimeMs;
+ public int numRunningTopEj;
+
+ void clear() {
+ minPreferredUidOnlyWaitingTimeMs = 0;
+ numRunningTopEj = 0;
+ }
+ }
+
// TESTING HELPERS
@VisibleForTesting
@@ -2406,6 +2562,15 @@
final PackageStats packageStats =
getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName());
packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob());
+
+ final JobServiceContext context;
+ if (mIdleContexts.size() > 0) {
+ context = mIdleContexts.removeAt(mIdleContexts.size() - 1);
+ } else {
+ context = createNewJobServiceContext();
+ }
+ context.executeRunnableJob(job, mWorkCountTracker.canJobStart(getJobWorkTypes(job)));
+ mActiveServices.add(context);
}
@VisibleForTesting
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 d9fb318..ee08f85 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -47,6 +47,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -177,7 +178,7 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static Clock sSystemClock = Clock.systemUTC();
private abstract static class MySimpleClock extends Clock {
@@ -454,6 +455,10 @@
runtimeUpdated = true;
}
break;
+ case Constants.KEY_PERSIST_IN_SPLIT_FILES:
+ mConstants.updatePersistingConstantsLocked();
+ mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES);
+ break;
default:
if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
&& !concurrencyUpdated) {
@@ -537,6 +542,8 @@
private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
"runtime_min_high_priority_guarantee_ms";
+ private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
+
private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
@@ -563,6 +570,7 @@
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
+ static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
/**
@@ -678,6 +686,12 @@
DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
/**
+ * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
+ * saved in a single file.
+ */
+ public boolean PERSIST_IN_SPLIT_FILES = DEFAULT_PERSIST_IN_SPLIT_FILES;
+
+ /**
* If true, use TARE policy for job limiting. If false, use quotas.
*/
public boolean USE_TARE_POLICY = DEFAULT_USE_TARE_POLICY;
@@ -735,6 +749,11 @@
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
}
+ private void updatePersistingConstantsLocked() {
+ PERSIST_IN_SPLIT_FILES = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_PERSIST_IN_SPLIT_FILES, DEFAULT_PERSIST_IN_SPLIT_FILES);
+ }
+
private void updatePrefetchConstantsLocked() {
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
@@ -835,6 +854,8 @@
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.println();
+ pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
+
pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println();
pw.decreaseIndent();
@@ -1290,7 +1311,16 @@
jobStatus.getJob().isPrefetch(),
jobStatus.getJob().getPriority(),
jobStatus.getEffectivePriority(),
- jobStatus.getNumPreviousAttempts());
+ jobStatus.getNumPreviousAttempts(),
+ jobStatus.getJob().getMaxExecutionDelayMillis(),
+ /* isDeadlineConstraintSatisfied */ false,
+ /* isCharging */ false,
+ /* batteryNotLow */ false,
+ /* storageNotLow */false,
+ /* timingDelayConstraintSatisfied */ false,
+ /* isDeviceIdle */ false,
+ /* hasConnectivityConstraintSatisfied */ false,
+ /* hasContentTriggerConstraintSatisfied */ false);
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1489,7 +1519,16 @@
cancelled.getJob().isPrefetch(),
cancelled.getJob().getPriority(),
cancelled.getEffectivePriority(),
- cancelled.getNumPreviousAttempts());
+ cancelled.getNumPreviousAttempts(),
+ cancelled.getJob().getMaxExecutionDelayMillis(),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE),
+ cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_CHARGING),
+ cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW),
+ cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
+ cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -3371,6 +3410,39 @@
}
}
+ @Override
+ public boolean canRunLongJobs(@NonNull String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId);
+ if (callingUid != packageUid) {
+ throw new SecurityException("Uid " + callingUid
+ + " cannot query canRunLongJobs for package " + packageName);
+ }
+
+ return checkRunLongJobsPermission(packageUid, packageName);
+ }
+
+ @Override
+ public boolean hasRunLongJobsPermission(@NonNull String packageName,
+ @UserIdInt int userId) {
+ final int uid = mLocalPM.getPackageUid(packageName, 0, userId);
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != uid && !UserHandle.isCore(callingUid)) {
+ throw new SecurityException("Uid " + callingUid
+ + " cannot query canRunLongJobs for package " + packageName);
+ }
+
+ return checkRunLongJobsPermission(uid, packageName);
+ }
+
+ private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
+ // Returns true if both the appop and permission are granted.
+ return PermissionChecker.checkPermissionForPreflight(getContext(),
+ android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
+ packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
+ }
+
/**
* "dumpsys" infrastructure
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 9e3f19d..9aa6b1c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -21,6 +21,7 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.IJobCallback;
@@ -187,6 +188,18 @@
public long mStoppedTime;
@Override
+ public void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+ @BytesLong long transferredBytes) {
+ doAcknowledgeGetTransferredDownloadBytesMessage(this, jobId, workId, transferredBytes);
+ }
+
+ @Override
+ public void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId,
+ @BytesLong long transferredBytes) {
+ doAcknowledgeGetTransferredUploadBytesMessage(this, jobId, workId, transferredBytes);
+ }
+
+ @Override
public void acknowledgeStartMessage(int jobId, boolean ongoing) {
doAcknowledgeStartMessage(this, jobId, ongoing);
}
@@ -210,6 +223,18 @@
public void jobFinished(int jobId, boolean reschedule) {
doJobFinished(this, jobId, reschedule);
}
+
+ @Override
+ public void updateEstimatedNetworkBytes(int jobId, JobWorkItem item,
+ long downloadBytes, long uploadBytes) {
+ doUpdateEstimatedNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+ }
+
+ @Override
+ public void updateTransferredNetworkBytes(int jobId, JobWorkItem item,
+ long downloadBytes, long uploadBytes) {
+ doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+ }
}
JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
@@ -363,7 +388,16 @@
job.getJob().isPrefetch(),
job.getJob().getPriority(),
job.getEffectivePriority(),
- job.getNumPreviousAttempts());
+ job.getNumPreviousAttempts(),
+ job.getJob().getMaxExecutionDelayMillis(),
+ isDeadlineExpired,
+ job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_CHARGING),
+ job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW),
+ job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW),
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
+ job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
// Use the context's ID to distinguish traces since there'll only be one job
// running per context.
@@ -454,6 +488,10 @@
return mTimeoutElapsed;
}
+ long getRemainingGuaranteedTimeMs(long nowElapsed) {
+ return Math.max(0, mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - nowElapsed);
+ }
+
boolean isWithinExecutionGuaranteeTime() {
return sElapsedRealtimeClock.millis()
< mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
@@ -493,6 +531,16 @@
}
}
+ private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback jobCallback, int jobId,
+ int workId, @BytesLong long transferredBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
+ private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback jobCallback, int jobId,
+ int workId, @BytesLong long transferredBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
doCallback(cb, reschedule, null);
}
@@ -545,6 +593,16 @@
}
}
+ private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
+ private void doUpdateEstimatedNetworkBytes(JobCallback jobCallback, int jobId,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
/**
* We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
* we intend to send to the client - we stop sending work when the service is unbound so until
@@ -1032,7 +1090,16 @@
completedJob.getJob().isPrefetch(),
completedJob.getJob().getPriority(),
completedJob.getEffectivePriority(),
- completedJob.getNumPreviousAttempts());
+ completedJob.getNumPreviousAttempts(),
+ completedJob.getJob().getMaxExecutionDelayMillis(),
+ mParams.isOverrideDeadlineExpired(),
+ completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_CHARGING),
+ completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW),
+ completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW),
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
+ completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
completedJob.getTag(), getId());
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 68cb049..c2602f2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -40,6 +40,7 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SystemConfigFileCommitEventLogger;
import android.util.Xml;
@@ -47,6 +48,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
+import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
@@ -89,6 +91,8 @@
/** Threshold to adjust how often we want to write to the db. */
private static final long JOB_PERSIST_DELAY = 2000L;
+ private static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+ private static final int ALL_UIDS = -1;
final Object mLock;
final Object mWriteScheduleLock; // used solely for invariants around write scheduling
@@ -105,13 +109,20 @@
@GuardedBy("mWriteScheduleLock")
private boolean mWriteInProgress;
+ @GuardedBy("mWriteScheduleLock")
+ private boolean mSplitFileMigrationNeeded;
+
private static final Object sSingletonLock = new Object();
private final SystemConfigFileCommitEventLogger mEventLogger;
private final AtomicFile mJobsFile;
+ private final File mJobFileDirectory;
+ private final SparseBooleanArray mPendingJobWriteUids = new SparseBooleanArray();
/** Handler backed by IoThread for writing to disk. */
private final Handler mIoHandler = IoThread.getHandler();
private static JobStore sSingleton;
+ private boolean mUseSplitFiles = JobSchedulerService.Constants.DEFAULT_PERSIST_IN_SPLIT_FILES;
+
private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
@@ -144,10 +155,10 @@
mContext = context;
File systemDir = new File(dataDir, "system");
- File jobDir = new File(systemDir, "job");
- jobDir.mkdirs();
+ mJobFileDirectory = new File(systemDir, "job");
+ mJobFileDirectory.mkdirs();
mEventLogger = new SystemConfigFileCommitEventLogger("jobs");
- mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), mEventLogger);
+ mJobsFile = createJobFile(new File(mJobFileDirectory, "jobs.xml"));
mJobSet = new JobSet();
@@ -162,12 +173,21 @@
// an incorrect historical timestamp. That's fine; at worst we'll reboot with
// a *correct* timestamp, see a bunch of overdue jobs, and run them; then
// settle into normal operation.
- mXmlTimestamp = mJobsFile.getLastModifiedTime();
+ mXmlTimestamp = mJobsFile.exists()
+ ? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified();
mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
readJobMapFromDisk(mJobSet, mRtcGood);
}
+ private AtomicFile createJobFile(String baseName) {
+ return createJobFile(new File(mJobFileDirectory, baseName + ".xml"));
+ }
+
+ private AtomicFile createJobFile(File file) {
+ return new AtomicFile(file, mEventLogger);
+ }
+
public boolean jobTimesInflatedValid() {
return mRtcGood;
}
@@ -211,6 +231,7 @@
public void add(JobStatus jobStatus) {
mJobSet.add(jobStatus);
if (jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
maybeWriteStatusToDiskAsync();
}
if (DEBUG) {
@@ -224,6 +245,9 @@
@VisibleForTesting
public void addForTesting(JobStatus jobStatus) {
mJobSet.add(jobStatus);
+ if (jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
+ }
}
boolean containsJob(JobStatus jobStatus) {
@@ -257,12 +281,24 @@
return false;
}
if (removeFromPersisted && jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
maybeWriteStatusToDiskAsync();
}
return removed;
}
/**
+ * Like {@link #remove(JobStatus, boolean)}, but doesn't schedule a disk write.
+ */
+ @VisibleForTesting
+ public void removeForTesting(JobStatus jobStatus) {
+ mJobSet.remove(jobStatus);
+ if (jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
+ }
+ }
+
+ /**
* Remove the jobs of users not specified in the keepUserIds.
* @param keepUserIds Array of User IDs whose jobs should be kept and not removed.
*/
@@ -273,6 +309,7 @@
@VisibleForTesting
public void clear() {
mJobSet.clear();
+ mPendingJobWriteUids.put(ALL_UIDS, true);
maybeWriteStatusToDiskAsync();
}
@@ -282,6 +319,36 @@
@VisibleForTesting
public void clearForTesting() {
mJobSet.clear();
+ mPendingJobWriteUids.put(ALL_UIDS, true);
+ }
+
+ void setUseSplitFiles(boolean useSplitFiles) {
+ synchronized (mLock) {
+ if (mUseSplitFiles != useSplitFiles) {
+ mUseSplitFiles = useSplitFiles;
+ migrateJobFilesAsync();
+ }
+ }
+ }
+
+ /**
+ * The same as above but does not schedule writing. This makes perf benchmarks more stable.
+ */
+ @VisibleForTesting
+ public void setUseSplitFilesForTesting(boolean useSplitFiles) {
+ final boolean changed;
+ synchronized (mLock) {
+ changed = mUseSplitFiles != useSplitFiles;
+ if (changed) {
+ mUseSplitFiles = useSplitFiles;
+ mPendingJobWriteUids.put(ALL_UIDS, true);
+ }
+ }
+ if (changed) {
+ synchronized (mWriteScheduleLock) {
+ mSplitFileMigrationNeeded = true;
+ }
+ }
}
/**
@@ -352,6 +419,16 @@
private static final String XML_TAG_ONEOFF = "one-off";
private static final String XML_TAG_EXTRAS = "extras";
+ private void migrateJobFilesAsync() {
+ synchronized (mLock) {
+ mPendingJobWriteUids.put(ALL_UIDS, true);
+ }
+ synchronized (mWriteScheduleLock) {
+ mSplitFileMigrationNeeded = true;
+ maybeWriteStatusToDiskAsync();
+ }
+ }
+
/**
* Every time the state changes we write all the jobs in one swath, instead of trying to
* track incremental changes.
@@ -449,10 +526,38 @@
* NOTE: This Runnable locks on mLock
*/
private final Runnable mWriteRunnable = new Runnable() {
+ private final SparseArray<AtomicFile> mJobFiles = new SparseArray<>();
+ private final CopyConsumer mPersistedJobCopier = new CopyConsumer();
+
+ class CopyConsumer implements Consumer<JobStatus> {
+ private final SparseArray<List<JobStatus>> mJobStoreCopy = new SparseArray<>();
+ private boolean mCopyAllJobs;
+
+ private void prepare() {
+ mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS);
+ }
+
+ @Override
+ public void accept(JobStatus jobStatus) {
+ final int uid = mUseSplitFiles ? jobStatus.getUid() : ALL_UIDS;
+ if (jobStatus.isPersisted() && (mCopyAllJobs || mPendingJobWriteUids.get(uid))) {
+ List<JobStatus> uidJobList = mJobStoreCopy.get(uid);
+ if (uidJobList == null) {
+ uidJobList = new ArrayList<>();
+ mJobStoreCopy.put(uid, uidJobList);
+ }
+ uidJobList.add(new JobStatus(jobStatus));
+ }
+ }
+
+ private void reset() {
+ mJobStoreCopy.clear();
+ }
+ }
+
@Override
public void run() {
final long startElapsed = sElapsedRealtimeClock.millis();
- final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
// Intentionally allow new scheduling of a write operation *before* we clone
// the job set. If we reset it to false after cloning, there's a window in
// which no new write will be scheduled but mLock is not held, i.e. a new
@@ -469,31 +574,73 @@
}
mWriteInProgress = true;
}
+ final boolean useSplitFiles;
synchronized (mLock) {
// Clone the jobs so we can release the lock before writing.
- mJobSet.forEachJob(null, (job) -> {
- if (job.isPersisted()) {
- storeCopy.add(new JobStatus(job));
- }
- });
+ useSplitFiles = mUseSplitFiles;
+ mPersistedJobCopier.prepare();
+ mJobSet.forEachJob(null, mPersistedJobCopier);
+ mPendingJobWriteUids.clear();
}
- writeJobsMapImpl(storeCopy);
+ mPersistInfo.countAllJobsSaved = 0;
+ mPersistInfo.countSystemServerJobsSaved = 0;
+ mPersistInfo.countSystemSyncManagerJobsSaved = 0;
+ for (int i = mPersistedJobCopier.mJobStoreCopy.size() - 1; i >= 0; --i) {
+ AtomicFile file;
+ if (useSplitFiles) {
+ final int uid = mPersistedJobCopier.mJobStoreCopy.keyAt(i);
+ file = mJobFiles.get(uid);
+ if (file == null) {
+ file = createJobFile(JOB_FILE_SPLIT_PREFIX + uid);
+ mJobFiles.put(uid, file);
+ }
+ } else {
+ file = mJobsFile;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Writing for " + mPersistedJobCopier.mJobStoreCopy.keyAt(i)
+ + " to " + file.getBaseFile().getName() + ": "
+ + mPersistedJobCopier.mJobStoreCopy.valueAt(i).size() + " jobs");
+ }
+ writeJobsMapImpl(file, mPersistedJobCopier.mJobStoreCopy.valueAt(i));
+ }
if (DEBUG) {
Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
- startElapsed) + "ms");
}
+ mPersistedJobCopier.reset();
+ if (!useSplitFiles) {
+ mJobFiles.clear();
+ }
+ // Update the last modified time of the directory to aid in RTC time verification
+ // (see the JobStore constructor).
+ mJobFileDirectory.setLastModified(sSystemClock.millis());
synchronized (mWriteScheduleLock) {
+ if (mSplitFileMigrationNeeded) {
+ final File[] files = mJobFileDirectory.listFiles();
+ for (File file : files) {
+ if (useSplitFiles) {
+ if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // Delete the now unused file so there's no confusion in the future.
+ file.delete();
+ }
+ } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // Delete the now unused file so there's no confusion in the future.
+ file.delete();
+ }
+ }
+ }
mWriteInProgress = false;
mWriteScheduleLock.notifyAll();
}
}
- private void writeJobsMapImpl(List<JobStatus> jobList) {
+ private void writeJobsMapImpl(@NonNull AtomicFile file, @NonNull List<JobStatus> jobList) {
int numJobs = 0;
int numSystemJobs = 0;
int numSyncJobs = 0;
mEventLogger.setStartTime(SystemClock.uptimeMillis());
- try (FileOutputStream fos = mJobsFile.startWrite()) {
+ try (FileOutputStream fos = file.startWrite()) {
TypedXmlSerializer out = Xml.resolveSerializer(fos);
out.startDocument(null, true);
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
@@ -523,7 +670,7 @@
out.endTag(null, "job-info");
out.endDocument();
- mJobsFile.finishWrite(fos);
+ file.finishWrite(fos);
} catch (IOException e) {
if (DEBUG) {
Slog.v(TAG, "Error writing out job data.", e);
@@ -533,9 +680,9 @@
Slog.d(TAG, "Error persisting bundle.", e);
}
} finally {
- mPersistInfo.countAllJobsSaved = numJobs;
- mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
- mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
+ mPersistInfo.countAllJobsSaved += numJobs;
+ mPersistInfo.countSystemServerJobsSaved += numSystemJobs;
+ mPersistInfo.countSystemSyncManagerJobsSaved += numSyncJobs;
}
}
@@ -600,9 +747,11 @@
* because currently store is not including everything (like, UIDs, bandwidth,
* signal strength etc. are lost).
*/
- private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
+ private void writeConstraintsToXml(TypedXmlSerializer out, JobStatus jobStatus)
+ throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
if (jobStatus.hasConnectivityConstraint()) {
+ final JobInfo job = jobStatus.getJob();
final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
out.attribute(null, "net-capabilities-csv", intArrayToString(
network.getCapabilities()));
@@ -610,6 +759,18 @@
network.getForbiddenCapabilities()));
out.attribute(null, "net-transport-types-csv", intArrayToString(
network.getTransportTypes()));
+ if (job.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ out.attributeLong(null, "estimated-download-bytes",
+ job.getEstimatedNetworkDownloadBytes());
+ }
+ if (job.getEstimatedNetworkUploadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ out.attributeLong(null, "estimated-upload-bytes",
+ job.getEstimatedNetworkUploadBytes());
+ }
+ if (job.getMinimumNetworkChunkBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ out.attributeLong(null, "minimum-network-chunk-bytes",
+ job.getMinimumNetworkChunkBytes());
+ }
}
if (jobStatus.hasIdleConstraint()) {
out.attribute(null, "idle", Boolean.toString(true));
@@ -720,54 +881,87 @@
@Override
public void run() {
+ if (!mJobFileDirectory.isDirectory()) {
+ Slog.wtf(TAG, "jobs directory isn't a directory O.O");
+ mJobFileDirectory.mkdirs();
+ return;
+ }
+
int numJobs = 0;
int numSystemJobs = 0;
int numSyncJobs = 0;
List<JobStatus> jobs;
- try (FileInputStream fis = mJobsFile.openRead()) {
- synchronized (mLock) {
- jobs = readJobMapImpl(fis, rtcGood);
- if (jobs != null) {
- long now = sElapsedRealtimeClock.millis();
- for (int i=0; i<jobs.size(); i++) {
- JobStatus js = jobs.get(i);
- js.prepareLocked();
- js.enqueueTime = now;
- this.jobSet.add(js);
+ final File[] files;
+ try {
+ files = mJobFileDirectory.listFiles();
+ } catch (SecurityException e) {
+ Slog.wtf(TAG, "Not allowed to read job file directory", e);
+ return;
+ }
+ if (files == null) {
+ Slog.wtfStack(TAG, "Couldn't get job file list");
+ return;
+ }
+ boolean needFileMigration = false;
+ long now = sElapsedRealtimeClock.millis();
+ for (File file : files) {
+ final AtomicFile aFile = createJobFile(file);
+ try (FileInputStream fis = aFile.openRead()) {
+ synchronized (mLock) {
+ jobs = readJobMapImpl(fis, rtcGood);
+ if (jobs != null) {
+ for (int i = 0; i < jobs.size(); i++) {
+ JobStatus js = jobs.get(i);
+ js.prepareLocked();
+ js.enqueueTime = now;
+ this.jobSet.add(js);
- numJobs++;
- if (js.getUid() == Process.SYSTEM_UID) {
- numSystemJobs++;
- if (isSyncJob(js)) {
- numSyncJobs++;
+ numJobs++;
+ if (js.getUid() == Process.SYSTEM_UID) {
+ numSystemJobs++;
+ if (isSyncJob(js)) {
+ numSyncJobs++;
+ }
}
}
}
}
+ } catch (FileNotFoundException e) {
+ // mJobFileDirectory.listFiles() gave us this file...why can't we find it???
+ Slog.e(TAG, "Could not find jobs file: " + file.getName());
+ } catch (XmlPullParserException | IOException e) {
+ Slog.wtf(TAG, "Error in " + file.getName(), e);
+ } catch (Exception e) {
+ // Crashing at this point would result in a boot loop, so live with a general
+ // Exception for system stability's sake.
+ Slog.wtf(TAG, "Unexpected exception", e);
}
- } catch (FileNotFoundException e) {
- if (DEBUG) {
- Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
- }
- } catch (XmlPullParserException | IOException e) {
- Slog.wtf(TAG, "Error jobstore xml.", e);
- } catch (Exception e) {
- // Crashing at this point would result in a boot loop, so live with a general
- // Exception for system stability's sake.
- Slog.wtf(TAG, "Unexpected exception", e);
- } finally {
- if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
- mPersistInfo.countAllJobsLoaded = numJobs;
- mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
- mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+ if (mUseSplitFiles) {
+ if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // We're supposed to be using the split file architecture, but we still have
+ // the old job file around. Fully migrate and remove the old file.
+ needFileMigration = true;
+ }
+ } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // We're supposed to be using the legacy single file architecture, but we still
+ // have some job split files around. Fully migrate and remove the split files.
+ needFileMigration = true;
}
}
+ if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
+ mPersistInfo.countAllJobsLoaded = numJobs;
+ mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
+ mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+ }
Slog.i(TAG, "Read " + numJobs + " jobs");
+ if (needFileMigration) {
+ migrateJobFilesAsync();
+ }
}
private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood)
throws XmlPullParserException, IOException {
- XmlPullParser parser = Xml.resolvePullParser(fis);
+ TypedXmlPullParser parser = Xml.resolvePullParser(fis);
int eventType = parser.getEventType();
while (eventType != XmlPullParser.START_TAG &&
@@ -827,7 +1021,7 @@
* will take the parser into the body of the job tag.
* @return Newly instantiated job holding all the information we just read out of the xml tag.
*/
- private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser,
+ private JobStatus restoreJobFromXml(boolean rtcIsGood, TypedXmlPullParser parser,
int schemaVersion) throws XmlPullParserException, IOException {
JobInfo.Builder jobBuilder;
int uid, sourceUserId;
@@ -1073,7 +1267,7 @@
* reading, but in order to avoid issues with OEM-defined flags, the accepted capabilities
* are limited to that(maxNetCapabilityInR & maxTransportInR) defined in R.
*/
- private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser)
+ private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
String val;
String netCapabilitiesLong = null;
@@ -1110,7 +1304,17 @@
for (int transport : stringToIntArray(netTransportTypesIntArray)) {
builder.addTransportType(transport);
}
- jobBuilder.setRequiredNetwork(builder.build());
+ jobBuilder
+ .setRequiredNetwork(builder.build())
+ .setEstimatedNetworkBytes(
+ parser.getAttributeLong(null,
+ "estimated-download-bytes", JobInfo.NETWORK_BYTES_UNKNOWN),
+ parser.getAttributeLong(null,
+ "estimated-upload-bytes", JobInfo.NETWORK_BYTES_UNKNOWN))
+ .setMinimumNetworkChunkBytes(
+ parser.getAttributeLong(null,
+ "minimum-network-chunk-bytes",
+ JobInfo.NETWORK_BYTES_UNKNOWN));
} else if (netCapabilitiesLong != null && netTransportTypesLong != null) {
// Format used on R- builds. Drop any unexpected capabilities and transports.
final NetworkRequest.Builder builder = new NetworkRequest.Builder()
@@ -1138,6 +1342,8 @@
}
}
jobBuilder.setRequiredNetwork(builder.build());
+ // Estimated bytes weren't persisted on R- builds, so no point querying for the
+ // attributes here.
} else {
// Read legacy values
val = parser.getAttributeValue(null, "connectivity");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 92716f4..da20684 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -95,11 +95,11 @@
static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
- static final int CONSTRAINT_TIMING_DELAY = 1<<31;
- static final int CONSTRAINT_DEADLINE = 1<<30;
- static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
+ public static final int CONSTRAINT_TIMING_DELAY = 1 << 31;
+ public static final int CONSTRAINT_DEADLINE = 1 << 30;
+ public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
static final int CONSTRAINT_TARE_WEALTH = 1 << 27; // Implicit constraint
- static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
+ public static final int CONSTRAINT_CONTENT_TRIGGER = 1 << 26;
static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
static final int CONSTRAINT_PREFETCH = 1 << 23;
@@ -762,7 +762,6 @@
executingWork.add(work);
work.bumpDeliveryCount();
}
- updateNetworkBytesLocked();
return work;
}
return null;
@@ -790,6 +789,7 @@
if (work.getWorkId() == workId) {
executingWork.remove(i);
ungrantWorkItem(work);
+ updateNetworkBytesLocked();
return true;
}
}
@@ -1613,7 +1613,8 @@
}
}
- boolean isConstraintSatisfied(int constraint) {
+ /** @return whether or not the @param constraint is satisfied */
+ public boolean isConstraintSatisfied(int constraint) {
return (satisfiedConstraints&constraint) != 0;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index 61d7470..fc60228 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -161,6 +161,7 @@
// Scheduler.
mStateChangedListener.onRunJobNow(job);
}
+ mTrackedJobs.remove(job);
Counter.logIncrement(
"job_scheduler.value_job_scheduler_job_deadline_expired_counter");
} else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
@@ -174,8 +175,11 @@
&& job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
// Since this is just the delay, we don't need to rush the Scheduler to run the job
// immediately if the constraint is satisfied here.
- if (!evaluateTimingDelayConstraint(job, nowElapsedMillis)
- && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+ if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
+ if (canStopTrackingJobLocked(job)) {
+ mTrackedJobs.remove(job);
+ }
+ } else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
// This job's delay is earlier than the current set alarm. Update the alarm.
setDelayExpiredAlarmLocked(job.getEarliestRunTime(),
mService.deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
diff --git a/api/Android.bp b/api/Android.bp
index a3e64a5..b0ce9af 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -99,7 +99,6 @@
"framework-connectivity",
"framework-connectivity-t",
"framework-devicelock",
- "framework-federatedcompute",
"framework-graphics",
"framework-healthconnect",
"framework-media",
@@ -113,6 +112,7 @@
"framework-sdksandbox",
"framework-tethering",
"framework-uwb",
+ "framework-virtualization",
"framework-wifi",
"i18n.module.public.api",
],
diff --git a/api/api.go b/api/api.go
index 6a6c493..ba0fdc1 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,8 +27,16 @@
const art = "art.module.public.api"
const conscrypt = "conscrypt.module.public.api"
const i18n = "i18n.module.public.api"
+const virtualization = "framework-virtualization"
var core_libraries_modules = []string{art, conscrypt, i18n}
+// List of modules that are not yet updatable, and hence they can still compile
+// against hidden APIs. These modules are filtered out when building the
+// updatable-framework-module-impl (because updatable-framework-module-impl is
+// built against module_current SDK). Instead they are directly statically
+// linked into the all-framework-module-lib, which is building against hidden
+// APIs.
+var non_updatable_modules = []string{virtualization}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
@@ -249,12 +257,31 @@
func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
// This module is for the "framework-all" module, which should not include the core libraries.
modules = removeAll(modules, core_libraries_modules)
- props := libraryProps{}
- props.Name = proptools.StringPtr("all-framework-module-impl")
- props.Static_libs = transformArray(modules, "", ".impl")
- props.Sdk_version = proptools.StringPtr("module_current")
- props.Visibility = []string{"//frameworks/base"}
- ctx.CreateModule(java.LibraryFactory, &props)
+ // Remove the modules that belong to non-updatable APEXes since those are allowed to compile
+ // against unstable APIs.
+ modules = removeAll(modules, non_updatable_modules)
+ // First create updatable-framework-module-impl, which contains all updatable modules.
+ // This module compiles against module_lib SDK.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("updatable-framework-module-impl")
+ props.Static_libs = transformArray(modules, "", ".impl")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+
+ // Now create all-framework-module-impl, which contains updatable-framework-module-impl
+ // and all non-updatable modules. This module compiles against hidden APIs.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-framework-module-impl")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".impl")
+ props.Static_libs = append(props.Static_libs, "updatable-framework-module-impl")
+ props.Sdk_version = proptools.StringPtr("core_platform")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
}
func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
diff --git a/boot/Android.bp b/boot/Android.bp
index 7839918..c9a3bd0 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -76,10 +76,6 @@
module: "com.android.devicelock-bootclasspath-fragment",
},
{
- apex: "com.android.federatedcompute",
- module: "com.android.federatedcompute-bootclasspath-fragment",
- },
- {
apex: "com.android.healthconnect",
module: "com.android.healthconnect-bootclasspath-fragment",
},
@@ -136,6 +132,10 @@
apex: "com.android.car.framework",
module: "com.android.car.framework-bootclasspath-fragment",
},
+ {
+ apex: "com.android.virt",
+ module: "com.android.virt-bootclasspath-fragment",
+ },
],
// Additional information needed by hidden api processing.
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 8be8cda..c4d90c6 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -130,14 +130,14 @@
uniform sampler2D uTexture;
uniform float uFade;
uniform float uColorProgress;
- uniform vec4 uStartColor0;
- uniform vec4 uStartColor1;
- uniform vec4 uStartColor2;
- uniform vec4 uStartColor3;
- uniform vec4 uEndColor0;
- uniform vec4 uEndColor1;
- uniform vec4 uEndColor2;
- uniform vec4 uEndColor3;
+ uniform vec3 uStartColor0;
+ uniform vec3 uStartColor1;
+ uniform vec3 uStartColor2;
+ uniform vec3 uStartColor3;
+ uniform vec3 uEndColor0;
+ uniform vec3 uEndColor1;
+ uniform vec3 uEndColor2;
+ uniform vec3 uEndColor3;
varying highp vec2 vUv;
void main() {
vec4 mask = texture2D(uTexture, vUv);
@@ -150,12 +150,12 @@
* step(cWhiteMaskThreshold, g)
* step(cWhiteMaskThreshold, b)
* step(cWhiteMaskThreshold, a);
- vec4 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
+ vec3 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
+ g * mix(uStartColor1, uEndColor1, uColorProgress)
+ b * mix(uStartColor2, uEndColor2, uColorProgress)
+ a * mix(uStartColor3, uEndColor3, uColorProgress);
- color = mix(color, vec4(vec3((r + g + b + a) * 0.25), 1.0), useWhiteMask);
- gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+ color = mix(color, vec3((r + g + b + a) * 0.25), useWhiteMask);
+ gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade));
})";
static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(
precision mediump float;
@@ -1439,12 +1439,12 @@
for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
float *startColor = mAnimation->startColors[i];
float *endColor = mAnimation->endColors[i];
- glUniform4f(glGetUniformLocation(mImageShader,
+ glUniform3f(glGetUniformLocation(mImageShader,
(U_START_COLOR_PREFIX + std::to_string(i)).c_str()),
- startColor[0], startColor[1], startColor[2], 1 /* alpha */);
- glUniform4f(glGetUniformLocation(mImageShader,
+ startColor[0], startColor[1], startColor[2]);
+ glUniform3f(glGetUniformLocation(mImageShader,
(U_END_COLOR_PREFIX + std::to_string(i)).c_str()),
- endColor[0], endColor[1], endColor[2], 1 /* alpha */);
+ endColor[0], endColor[1], endColor[2]);
}
mImageColorProgressLocation = glGetUniformLocation(mImageShader, U_COLOR_PROGRESS);
}
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 4f8faca..7a08cbd 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -222,6 +222,7 @@
},
data: [
"tests/data/**/*.apk",
+ "tests/data/**/*.png",
],
compile_multilib: "first",
test_options: {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 4431164..10947dc 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -39,6 +39,7 @@
#include "idmap2/PrettyPrintVisitor.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
+#include <fcntl.h>
using android::base::StringPrintf;
using android::binder::Status;
@@ -238,6 +239,9 @@
if (res.dataType == Res_value::TYPE_STRING) {
builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value(),
res.configuration.value_or(std::string()));
+ } else if (res.binaryData.has_value()) {
+ builder.SetResourceValue(res.resourceName, res.binaryData->get(),
+ res.configuration.value_or(std::string()));
} else {
builder.SetResourceValue(res.resourceName, res.dataType, res.data,
res.configuration.value_or(std::string()));
@@ -264,6 +268,7 @@
file_name.c_str(), kMaxFileNameLength));
}
} while (std::filesystem::exists(path));
+ builder.setFrroPath(path);
const uid_t uid = IPCThreadState::self()->getCallingUid();
if (!UidHasWriteAccessToPath(uid, path)) {
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index c773e11..3ad6d58 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -24,5 +24,6 @@
int dataType;
int data;
@nullable @utf8InCpp String stringData;
+ @nullable ParcelFileDescriptor binaryData;
@nullable @utf8InCpp String configuration;
}
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 05b0618..9f57710 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -28,6 +28,7 @@
#include "idmap2/ResourceContainer.h"
#include "idmap2/Result.h"
+#include <binder/ParcelFileDescriptor.h>
namespace android::idmap2 {
@@ -45,6 +46,15 @@
const std::string& data_string_value,
const std::string& configuration);
+ Builder& SetResourceValue(const std::string& resource_name,
+ std::optional<android::base::borrowed_fd>&& binary_value,
+ const std::string& configuration);
+
+ inline Builder& setFrroPath(std::string frro_path) {
+ frro_path_ = std::move(frro_path);
+ return *this;
+ }
+
WARN_UNUSED Result<FabricatedOverlay> Build();
private:
@@ -53,6 +63,7 @@
DataType data_type;
DataValue data_value;
std::string data_string_value;
+ std::optional<android::base::borrowed_fd> data_binary_value;
std::string configuration;
};
@@ -60,6 +71,7 @@
std::string name_;
std::string target_package_name_;
std::string target_overlayable_;
+ std::string frro_path_;
std::vector<Entry> entries_;
};
@@ -79,10 +91,14 @@
explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
std::string&& string_pool_data_,
+ std::vector<android::base::borrowed_fd> binary_files_,
+ off_t total_binary_bytes_,
std::optional<uint32_t> crc_from_disk = {});
pb::FabricatedOverlay overlay_pb_;
std::string string_pool_data_;
+ std::vector<android::base::borrowed_fd> binary_files_;
+ uint32_t total_binary_bytes_;
std::optional<uint32_t> crc_from_disk_;
mutable std::optional<SerializedData> data_;
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index af4dd89..2214a83 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -19,6 +19,7 @@
#include <optional>
#include <string>
+#include <android-base/unique_fd.h>
#include "androidfw/AssetManager2.h"
#include "idmap2/Result.h"
@@ -41,6 +42,7 @@
DataType data_type;
DataValue data_value;
std::string data_string_value;
+ std::optional<android::base::borrowed_fd> data_binary_value;
};
struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index bde9b0b..d517e29 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -16,6 +16,10 @@
#include "idmap2/FabricatedOverlay.h"
+#include <sys/stat.h> // umask
+#include <sys/types.h> // umask
+
+#include <android-base/file.h>
#include <androidfw/ResourceUtils.h>
#include <androidfw/StringPool.h>
#include <google/protobuf/io/coded_stream.h>
@@ -51,9 +55,13 @@
FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
std::string&& string_pool_data,
+ std::vector<android::base::borrowed_fd> binary_files,
+ off_t total_binary_bytes,
std::optional<uint32_t> crc_from_disk)
: overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
string_pool_data_(std::move(string_pool_data)),
+ binary_files_(std::move(binary_files)),
+ total_binary_bytes_(total_binary_bytes),
crc_from_disk_(crc_from_disk) {
}
@@ -72,14 +80,23 @@
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
const std::string& resource_name, uint8_t data_type, uint32_t data_value,
const std::string& configuration) {
- entries_.emplace_back(Entry{resource_name, data_type, data_value, "", configuration});
+ entries_.emplace_back(
+ Entry{resource_name, data_type, data_value, "", std::nullopt, configuration});
return *this;
}
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
const std::string& configuration) {
- entries_.emplace_back(Entry{resource_name, data_type, 0, data_string_value, configuration});
+ entries_.emplace_back(
+ Entry{resource_name, data_type, 0, data_string_value, std::nullopt, configuration});
+ return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+ const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
+ const std::string& configuration) {
+ entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, configuration});
return *this;
}
@@ -135,7 +152,7 @@
}
value->second = TargetValue{res_entry.data_type, res_entry.data_value,
- res_entry.data_string_value};
+ res_entry.data_string_value, res_entry.data_binary_value};
}
pb::FabricatedOverlay overlay_pb;
@@ -144,6 +161,11 @@
overlay_pb.set_target_package_name(target_package_name_);
overlay_pb.set_target_overlayable(target_overlayable_);
+ std::vector<android::base::borrowed_fd> binary_files;
+ size_t total_binary_bytes = 0;
+ // 16 for the number of bytes in the frro file before the binary data
+ const size_t FRRO_HEADER_SIZE = 16;
+
for (auto& package : package_map) {
auto package_pb = overlay_pb.add_packages();
package_pb->set_name(package.first);
@@ -162,6 +184,20 @@
if (value.second.data_type == Res_value::TYPE_STRING) {
auto ref = string_pool.MakeRef(value.second.data_string_value);
pb_value->set_data_value(ref.index());
+ } else if (value.second.data_binary_value.has_value()) {
+ pb_value->set_data_type(Res_value::TYPE_STRING);
+ struct stat s;
+ if (fstat(value.second.data_binary_value->get(), &s) == -1) {
+ return Error("unable to get size of binary file: %d", errno);
+ }
+ std::string uri
+ = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
+ static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
+ static_cast<int> (s.st_size));
+ total_binary_bytes += s.st_size;
+ binary_files.emplace_back(value.second.data_binary_value->get());
+ auto ref = string_pool.MakeRef(std::move(uri));
+ pb_value->set_data_value(ref.index());
} else {
pb_value->set_data_value(value.second.data_value);
}
@@ -169,10 +205,10 @@
}
}
}
-
android::BigBuffer string_buffer(kBufferSize);
android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
- return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
+ return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string(),
+ std::move(binary_files), total_binary_bytes);
}
Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -190,7 +226,7 @@
return Error("Failed to read fabricated overlay version.");
}
- if (version != 1 && version != 2) {
+ if (version < 1 || version > 3) {
return Error("Invalid fabricated overlay version '%u'.", version);
}
@@ -201,7 +237,14 @@
pb::FabricatedOverlay overlay{};
std::string sp_data;
- if (version == 2) {
+ uint32_t total_binary_bytes;
+ if (version == 3) {
+ if (!Read32(stream, &total_binary_bytes)) {
+ return Error("Failed read total binary bytes.");
+ }
+ stream.seekg(total_binary_bytes, std::istream::cur);
+ }
+ if (version >= 2) {
uint32_t sp_size;
if (!Read32(stream, &sp_size)) {
return Error("Failed read string pool size.");
@@ -211,20 +254,15 @@
return Error("Failed to read string pool.");
}
sp_data = buf;
-
- if (!overlay.ParseFromIstream(&stream)) {
- return Error("Failed read fabricated overlay proto.");
- }
- } else {
- if (!overlay.ParseFromIstream(&stream)) {
- return Error("Failed read fabricated overlay proto.");
- }
+ }
+ if (!overlay.ParseFromIstream(&stream)) {
+ return Error("Failed read fabricated overlay proto.");
}
// If the proto version is the latest version, then the contents of the proto must be the same
// when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
// proto to the latest version will likely change the contents of the fabricated overlay.
- return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+ return FabricatedOverlay(std::move(overlay), std::move(sp_data), {}, total_binary_bytes,
version == kFabricatedOverlayCurrentVersion
? std::optional<uint32_t>(crc)
: std::nullopt);
@@ -274,6 +312,14 @@
Write32(stream, kFabricatedOverlayMagic);
Write32(stream, kFabricatedOverlayCurrentVersion);
Write32(stream, (*data)->pb_crc);
+ Write32(stream, total_binary_bytes_);
+ std::string file_contents;
+ for (const android::base::borrowed_fd fd : binary_files_) {
+ if (!ReadFdToString(fd, &file_contents)) {
+ return Error("Failed to read binary file data.");
+ }
+ stream.write(file_contents.data(), file_contents.length());
+ }
Write32(stream, (*data)->sp_data.length());
stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
if (stream.bad()) {
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index e804c87..e13a0eb 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -17,6 +17,7 @@
#include <android-base/file.h>
#include <gtest/gtest.h>
#include <idmap2/FabricatedOverlay.h>
+#include "TestHelpers.h"
#include <fstream>
#include <utility>
@@ -41,6 +42,10 @@
}
TEST(FabricatedOverlayTests, SetResourceValue) {
+ auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+ auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
auto overlay =
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetResourceValue(
@@ -54,6 +59,8 @@
Res_value::TYPE_STRING,
"foobar",
"en-rUS-normal-xxhdpi-v21")
+ .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7")
+ .setFrroPath("/foo/bar/biz.frro")
.Build();
ASSERT_TRUE(overlay);
auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -67,19 +74,28 @@
auto pairs = container->GetOverlayData(*info);
ASSERT_TRUE(pairs);
- ASSERT_EQ(4U, pairs->pairs.size());
+ ASSERT_EQ(5U, pairs->pairs.size());
auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
pairs->string_pool_data->data_length, false);
auto& it = pairs->pairs[0];
- ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+ ASSERT_EQ("com.example.target:drawable/dr1", it.resource_name);
auto entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(std::string("frro://foo/bar/biz.frro?offset=16&size=8341"),
+ string_pool.string8At(entry->value.data_value).value_or(""));
+ ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
+ ASSERT_EQ("port-xxhdpi-v7", entry->config);
+
+ it = pairs->pairs[1];
+ ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+ entry = std::get_if<TargetValueWithConfig>(&it.value);
+ ASSERT_NE(nullptr, entry);
ASSERT_EQ(1U, entry->value.data_value);
ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
ASSERT_EQ("port", entry->config);
- it = pairs->pairs[1];
+ it = pairs->pairs[2];
ASSERT_EQ("com.example.target:string/int3", it.resource_name);
entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
@@ -87,7 +103,7 @@
ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->value.data_type);
ASSERT_EQ("xxhdpi-v7", entry->config);
- it = pairs->pairs[2];
+ it = pairs->pairs[3];
ASSERT_EQ("com.example.target:string/string1", it.resource_name);
entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
@@ -95,7 +111,7 @@
ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
ASSERT_EQ("en-rUS-normal-xxhdpi-v21", entry->config);
- it = pairs->pairs[3];
+ it = pairs->pairs[4];
ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 7b7dc17..b473f26 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -260,11 +260,17 @@
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
+ auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+ auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
.SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
+ .SetResourceValue("drawable/dr1", fd, "port-xxhdpi-v7")
+ .setFrroPath("/foo/bar/biz.frro")
.Build();
ASSERT_TRUE(frro);
@@ -293,14 +299,19 @@
auto string_pool_data = data->GetStringPoolData();
auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+ std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+ uint32_t uri_index
+ = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
const auto& target_inline_entries = data->GetTargetInlineEntries();
- ASSERT_EQ(target_inline_entries.size(), 3U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, "land-xxhdpi-v7",
+ ASSERT_EQ(target_inline_entries.size(), 4U);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::drawable::dr1, "port-xxhdpi-v7",
+ Res_value::TYPE_STRING, uri_index);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::integer::int1, "land-xxhdpi-v7",
Res_value::TYPE_INT_DEC, 2U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, "land",
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str1, "land",
Res_value::TYPE_REFERENCE, 0x7f010000);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, "xxhdpi-v7",
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[3], R::target::string::str2, "xxhdpi-v7",
Res_value::TYPE_STRING,
(uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
}
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index ad998b9..80c062d 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -26,24 +26,27 @@
// clang-format off
namespace R::target {
namespace integer { // NOLINT(runtime/indentation_namespace)
- constexpr ResourceId int1 = 0x7f010000;
+ constexpr ResourceId int1 = 0x7f020000;
+ }
+ namespace drawable {
+ constexpr ResourceId dr1 = 0x7f010000;
}
namespace string { // NOLINT(runtime/indentation_namespace)
- constexpr ResourceId not_overlayable = 0x7f020003;
- constexpr ResourceId other = 0x7f020004;
- constexpr ResourceId policy_actor = 0x7f020005;
- constexpr ResourceId policy_config_signature = 0x7f020006;
- constexpr ResourceId policy_odm = 0x7f020007;
- constexpr ResourceId policy_oem = 0x7f020008;
- constexpr ResourceId policy_product = 0x7f020009;
- constexpr ResourceId policy_public = 0x7f02000a;
- constexpr ResourceId policy_signature = 0x7f02000b;
- constexpr ResourceId policy_system = 0x7f02000c;
- constexpr ResourceId policy_system_vendor = 0x7f02000d;
- constexpr ResourceId str1 = 0x7f02000e;
- constexpr ResourceId str2 = 0x7f02000f;
- constexpr ResourceId str3 = 0x7f020010;
- constexpr ResourceId str4 = 0x7f020011;
+ constexpr ResourceId not_overlayable = 0x7f030003;
+ constexpr ResourceId other = 0x7f030004;
+ constexpr ResourceId policy_actor = 0x7f030005;
+ constexpr ResourceId policy_config_signature = 0x7f030006;
+ constexpr ResourceId policy_odm = 0x7f030007;
+ constexpr ResourceId policy_oem = 0x7f030008;
+ constexpr ResourceId policy_product = 0x7f030009;
+ constexpr ResourceId policy_public = 0x7f03000a;
+ constexpr ResourceId policy_signature = 0x7f03000b;
+ constexpr ResourceId policy_system = 0x7f03000c;
+ constexpr ResourceId policy_system_vendor = 0x7f03000d;
+ constexpr ResourceId str1 = 0x7f03000e;
+ constexpr ResourceId str2 = 0x7f03000f;
+ constexpr ResourceId str3 = 0x7f030010;
+ constexpr ResourceId str4 = 0x7f030011;
} // namespace string
} // namespace R::target
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 7112eeb..68164e2 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -79,22 +79,22 @@
ASSERT_CONTAINS_REGEX(ADDRESS "00000000 config count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "0000000a string pool index offset", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id: integer/int1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e target id: string/str1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e target id: string/str1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b overlay id: string/str1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020010 target id: string/str3", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030010 target id: string/str3", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c overlay id: string/str3", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020011 target id: string/str4", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030011 target id: string/str4", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d overlay id: string/str4", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id: integer/int1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b overlay id: string/str1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e target id: string/str1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e target id: string/str1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c overlay id: string/str3", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020010 target id: string/str3", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030010 target id: string/str3", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d overlay id: string/str4", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020011 target id: string/str4", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030011 target id: string/str4", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "000000b4 string pool size", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "........ string pool", stream.str());
}
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 016d427..380e462 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -23,6 +23,7 @@
#include <memory>
#include <string>
+#include <fcntl.h>
#include "R.h"
#include "TestConstants.h"
#include "TestHelpers.h"
@@ -76,7 +77,12 @@
auto target_map = mapping.GetTargetToOverlayMap();
auto entry_map = target_map.find(target_resource);
if (entry_map == target_map.end()) {
- return Error("Failed to find mapping for target resource");
+ std::string keys;
+ for (const auto &pair : target_map) {
+ keys.append(fmt::format("0x{:x}", pair.first)).append(" ");
+ }
+ return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+ target_resource, keys.c_str());
}
auto actual_overlay_resource = std::get_if<ResourceId>(&entry_map->second);
@@ -108,7 +114,12 @@
auto target_map = mapping.GetTargetToOverlayMap();
auto entry_map = target_map.find(target_resource);
if (entry_map == target_map.end()) {
- return Error("Failed to find mapping for target resource");
+ std::string keys;
+ for (const auto &pair : target_map) {
+ keys.append(fmt::format("{:x}", pair.first)).append(" ");
+ }
+ return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+ target_resource, keys.c_str());
}
auto config_map = std::get_if<ConfigMap>(&entry_map->second);
@@ -193,11 +204,16 @@
}
TEST(ResourceMappingTests, FabricatedOverlay) {
+ auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+ auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
.SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
+ .SetResourceValue("drawable/dr1", fd, "")
+ .setFrroPath("/foo/bar/biz.frro")
.Build();
ASSERT_TRUE(frro);
@@ -214,11 +230,16 @@
auto string_pool_data = res.GetStringPoolData();
auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
- ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+ std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+ uint32_t uri_index
+ = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
+
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
(uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
+ ASSERT_RESULT(MappingExists(res, R::target::drawable::dr1, Res_value::TYPE_STRING, uri_index));
ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
}
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
index d5799ad..794d622 100644
--- a/cmds/idmap2/tests/TestConstants.h
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -19,8 +19,8 @@
namespace android::idmap2::TestConstants {
-constexpr const auto TARGET_CRC = 0x7c2d4719;
-constexpr const auto TARGET_CRC_STRING = "7c2d4719";
+constexpr const auto TARGET_CRC = 0xa960a69;
+constexpr const auto TARGET_CRC_STRING = "0a960a69";
constexpr const auto OVERLAY_CRC = 0xb71095cf;
constexpr const auto OVERLAY_CRC_STRING = "b71095cf";
diff --git a/cmds/idmap2/tests/data/overlay/res/drawable/android.png b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
new file mode 100644
index 0000000..b7317b0
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build
index e6df742..cd13a7e 100755
--- a/cmds/idmap2/tests/data/target/build
+++ b/cmds/idmap2/tests/data/target/build
@@ -17,5 +17,7 @@
rm compiled.flata
aapt2 compile res/values/values.xml -o .
-aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat
-rm values_values.arsc.flat
\ No newline at end of file
+aapt2 compile res/drawable/dr1.png -o .
+aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat drawable_dr1.png.flat
+rm values_values.arsc.flat
+rm drawable_dr1.png.flat
diff --git a/cmds/idmap2/tests/data/target/res/drawable/dr1.png b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
new file mode 100644
index 0000000..1a56e68
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
index 57e6c43..aac9081 100644
--- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml
+++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
@@ -63,6 +63,7 @@
<item type="string" name="y" />
<item type="string" name="z" />
<item type="integer" name="int1" />
+ <item type="drawable" name="dr1" />
</policy>
</overlayable>
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index cc3491d..680eeb6 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index 4a58c5e..145e737 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/core/api/current.txt b/core/api/current.txt
index c824106..949c29a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -84,12 +84,25 @@
field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
+ field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
field public static final String DUMP = "android.permission.DUMP";
field public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
field public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
+ field public static final String FOREGROUND_SERVICE_CAMERA = "android.permission.FOREGROUND_SERVICE_CAMERA";
+ field public static final String FOREGROUND_SERVICE_CONNECTED_DEVICE = "android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE";
+ field public static final String FOREGROUND_SERVICE_DATA_SYNC = "android.permission.FOREGROUND_SERVICE_DATA_SYNC";
+ field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
+ field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
+ field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
+ field public static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
+ field public static final String FOREGROUND_SERVICE_MICROPHONE = "android.permission.FOREGROUND_SERVICE_MICROPHONE";
+ field public static final String FOREGROUND_SERVICE_PHONE_CALL = "android.permission.FOREGROUND_SERVICE_PHONE_CALL";
+ field public static final String FOREGROUND_SERVICE_REMOTE_MESSAGING = "android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING";
+ field public static final String FOREGROUND_SERVICE_SPECIAL_USE = "android.permission.FOREGROUND_SERVICE_SPECIAL_USE";
+ field public static final String FOREGROUND_SERVICE_SYSTEM_EXEMPTED = "android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED";
field public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -3074,6 +3087,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
@@ -3111,10 +3125,13 @@
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
+ method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1; // 0x1
field public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3; // 0x3
field public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4; // 0x4
+ field public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5; // 0x5
field public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2; // 0x2
+ field public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6; // 0x6
field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
@@ -5274,6 +5291,13 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceStartNotAllowedException> CREATOR;
}
+ public final class ForegroundServiceTypeNotAllowedException extends android.app.ServiceStartNotAllowedException implements android.os.Parcelable {
+ ctor public ForegroundServiceTypeNotAllowedException(@NonNull String);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceTypeNotAllowedException> CREATOR;
+ }
+
@Deprecated public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
ctor @Deprecated public Fragment();
method @Deprecated public void dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]);
@@ -6935,7 +6959,7 @@
method public void onTrimMemory(int);
method public boolean onUnbind(android.content.Intent);
method public final void startForeground(int, android.app.Notification);
- method public final void startForeground(int, @NonNull android.app.Notification, int);
+ method public final void startForeground(int, @NonNull android.app.Notification, @RequiresPermission int);
method @Deprecated public final void stopForeground(boolean);
method public final void stopForeground(int);
method public final void stopSelf();
@@ -7668,6 +7692,7 @@
method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
method public void wipeData(int);
method public void wipeData(int, @NonNull CharSequence);
+ method public void wipeDevice(int);
field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
@@ -8435,6 +8460,7 @@
public abstract class JobScheduler {
ctor public JobScheduler();
+ method public boolean canRunLongJobs();
method public abstract void cancel(int);
method public abstract void cancelAll();
method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
@@ -8447,19 +8473,31 @@
public abstract class JobService extends android.app.Service {
ctor public JobService();
+ method public long getTransferredDownloadBytes();
+ method public long getTransferredDownloadBytes(@NonNull android.app.job.JobWorkItem);
+ method public long getTransferredUploadBytes();
+ method public long getTransferredUploadBytes(@NonNull android.app.job.JobWorkItem);
method public final void jobFinished(android.app.job.JobParameters, boolean);
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
+ method public final void updateEstimatedNetworkBytes(@NonNull android.app.job.JobParameters, long, long);
+ method public final void updateEstimatedNetworkBytes(@NonNull android.app.job.JobParameters, @NonNull android.app.job.JobWorkItem, long, long);
+ method public final void updateTransferredNetworkBytes(@NonNull android.app.job.JobParameters, long, long);
+ method public final void updateTransferredNetworkBytes(@NonNull android.app.job.JobParameters, @NonNull android.app.job.JobWorkItem, long, long);
field public static final String PERMISSION_BIND = "android.permission.BIND_JOB_SERVICE";
}
public abstract class JobServiceEngine {
ctor public JobServiceEngine(android.app.Service);
method public final android.os.IBinder getBinder();
+ method public long getTransferredDownloadBytes(@NonNull android.app.job.JobParameters, @Nullable android.app.job.JobWorkItem);
+ method public long getTransferredUploadBytes(@NonNull android.app.job.JobParameters, @Nullable android.app.job.JobWorkItem);
method public void jobFinished(android.app.job.JobParameters, boolean);
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
+ method public void updateEstimatedNetworkBytes(@NonNull android.app.job.JobParameters, @NonNull android.app.job.JobWorkItem, long, long);
+ method public void updateTransferredNetworkBytes(@NonNull android.app.job.JobParameters, @Nullable android.app.job.JobWorkItem, long, long);
}
public final class JobWorkItem implements android.os.Parcelable {
@@ -9048,6 +9086,11 @@
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+ field public static final int RESULT_CANCELED = 0; // 0x0
+ field public static final int RESULT_DISCOVERY_TIMEOUT = 2; // 0x2
+ field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+ field public static final int RESULT_OK = -1; // 0xffffffff
+ field public static final int RESULT_USER_REJECTED = 1; // 0x1
}
public abstract static class CompanionDeviceManager.Callback {
@@ -10392,6 +10435,7 @@
field public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE";
field public static final String ACTION_SET_WALLPAPER = "android.intent.action.SET_WALLPAPER";
field public static final String ACTION_SHOW_APP_INFO = "android.intent.action.SHOW_APP_INFO";
+ field public static final String ACTION_SHOW_OUTPUT_SWITCHER = "android.intent.action.SHOW_OUTPUT_SWITCHER";
field public static final String ACTION_SHOW_WORK_APPS = "android.intent.action.SHOW_WORK_APPS";
field public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
field public static final String ACTION_SYNC = "android.intent.action.SYNC";
@@ -12108,6 +12152,7 @@
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
field public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
+ field public static final String FEATURE_UWB = "android.hardware.uwb";
field public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode";
@@ -12169,6 +12214,7 @@
field public static final int PERMISSION_DENIED = -1; // 0xffffffff
field public static final int PERMISSION_GRANTED = 0; // 0x0
field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
+ field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
field public static final int SIGNATURE_MATCH = 0; // 0x0
field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -12382,16 +12428,20 @@
field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
- field public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
- field public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
- field public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
- field public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
+ field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
- field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
- field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
- field public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
- field public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
- field public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
+ field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
field public int flags;
field public String permission;
}
@@ -16583,6 +16633,7 @@
field public static final int FONT_WEIGHT_NORMAL = 400; // 0x190
field public static final int FONT_WEIGHT_SEMI_BOLD = 600; // 0x258
field public static final int FONT_WEIGHT_THIN = 100; // 0x64
+ field public static final int FONT_WEIGHT_UNSPECIFIED = -1; // 0xffffffff
}
public final class FontVariationAxis {
@@ -17607,6 +17658,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Rational> CONTROL_AE_COMPENSATION_STEP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AE_LOCK_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AF_AVAILABLE_MODES;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AUTOFRAMING_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_EFFECTS;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.Capability[]> CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_CAPABILITIES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_MODES;
@@ -17770,6 +17822,7 @@
method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>);
method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
+ method public boolean isCaptureProcessProgressAvailable(int);
field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
field public static final int EXTENSION_BOKEH = 2; // 0x2
@@ -17789,6 +17842,7 @@
public abstract static class CameraExtensionSession.ExtensionCaptureCallback {
ctor public CameraExtensionSession.ExtensionCaptureCallback();
method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
+ method public void onCaptureProcessProgressed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @IntRange(from=0, to=100) int);
method public void onCaptureProcessStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
method public void onCaptureResultAvailable(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @NonNull android.hardware.camera2.TotalCaptureResult);
method public void onCaptureSequenceAborted(@NonNull android.hardware.camera2.CameraExtensionSession, int);
@@ -17909,6 +17963,12 @@
field public static final int CONTROL_AF_TRIGGER_CANCEL = 2; // 0x2
field public static final int CONTROL_AF_TRIGGER_IDLE = 0; // 0x0
field public static final int CONTROL_AF_TRIGGER_START = 1; // 0x1
+ field public static final int CONTROL_AUTOFRAMING_AUTO = 2; // 0x2
+ field public static final int CONTROL_AUTOFRAMING_OFF = 0; // 0x0
+ field public static final int CONTROL_AUTOFRAMING_ON = 1; // 0x1
+ field public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2; // 0x2
+ field public static final int CONTROL_AUTOFRAMING_STATE_FRAMING = 1; // 0x1
+ field public static final int CONTROL_AUTOFRAMING_STATE_INACTIVE = 0; // 0x0
field public static final int CONTROL_AWB_MODE_AUTO = 1; // 0x1
field public static final int CONTROL_AWB_MODE_CLOUDY_DAYLIGHT = 6; // 0x6
field public static final int CONTROL_AWB_MODE_DAYLIGHT = 5; // 0x5
@@ -18156,6 +18216,7 @@
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AF_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AF_TRIGGER;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AUTOFRAMING;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_AWB_LOCK;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AWB_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
@@ -18246,6 +18307,8 @@
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_SCENE_CHANGE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_STATE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_TRIGGER;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AUTOFRAMING;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AUTOFRAMING_STATE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_AWB_LOCK;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
@@ -32165,7 +32228,12 @@
public static class PerformanceHintManager.Session implements java.io.Closeable {
method public void close();
method public void reportActualWorkDuration(long);
+ method public void sendHint(int);
method public void updateTargetWorkDuration(long);
+ field public static final int CPU_LOAD_DOWN = 1; // 0x1
+ field public static final int CPU_LOAD_RESET = 2; // 0x2
+ field public static final int CPU_LOAD_RESUME = 3; // 0x3
+ field public static final int CPU_LOAD_UP = 0; // 0x0
}
public final class PersistableBundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -32264,6 +32332,7 @@
method public static final boolean is64Bit();
method public static boolean isApplicationUid(int);
method public static final boolean isIsolated();
+ method public static final boolean isIsolatedUid(int);
method public static final boolean isSdkSandbox();
method public static final void killProcess(int);
method public static final int myPid();
@@ -32538,7 +32607,7 @@
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
- method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.List<android.os.UserHandle> getVisibleUsers();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
method public boolean hasUserRestriction(String);
method public boolean isDemoUser();
method public static boolean isHeadlessSystemUserMode();
@@ -32615,6 +32684,7 @@
field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+ field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
@@ -35791,12 +35861,14 @@
field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
+ field public static final String ACTION_MANAGE_APP_LONG_JOBS = "android.settings.MANAGE_APP_LONG_JOBS";
field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
+ field public static final String ACTION_MEMTAG_SETTINGS = "android.settings.MEMTAG_SETTINGS";
field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS";
field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS";
field public static final String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS";
@@ -39903,7 +39975,7 @@
public abstract class WallpaperService extends android.app.Service {
ctor public WallpaperService();
method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
+ method @MainThread public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
field public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService";
field public static final String SERVICE_META_DATA = "android.service.wallpaper";
}
@@ -39918,20 +39990,20 @@
method public boolean isPreview();
method public boolean isVisible();
method public void notifyColorsChanged();
- method public void onApplyWindowInsets(android.view.WindowInsets);
- method public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
- method @Nullable public android.app.WallpaperColors onComputeColors();
- method public void onCreate(android.view.SurfaceHolder);
- method public void onDesiredSizeChanged(int, int);
- method public void onDestroy();
- method public void onOffsetsChanged(float, float, float, float, int, int);
- method public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
- method public void onSurfaceCreated(android.view.SurfaceHolder);
- method public void onSurfaceDestroyed(android.view.SurfaceHolder);
- method public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
- method public void onTouchEvent(android.view.MotionEvent);
- method public void onVisibilityChanged(boolean);
- method public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
+ method @MainThread public void onApplyWindowInsets(android.view.WindowInsets);
+ method @MainThread public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
+ method @MainThread @Nullable public android.app.WallpaperColors onComputeColors();
+ method @MainThread public void onCreate(android.view.SurfaceHolder);
+ method @MainThread public void onDesiredSizeChanged(int, int);
+ method @MainThread public void onDestroy();
+ method @MainThread public void onOffsetsChanged(float, float, float, float, int, int);
+ method @MainThread public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
+ method @MainThread public void onSurfaceCreated(android.view.SurfaceHolder);
+ method @MainThread public void onSurfaceDestroyed(android.view.SurfaceHolder);
+ method @MainThread public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
+ method @MainThread public void onTouchEvent(android.view.MotionEvent);
+ method @MainThread public void onVisibilityChanged(boolean);
+ method @MainThread public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
method public void setOffsetNotificationsEnabled(boolean);
method public void setTouchEventsEnabled(boolean);
}
@@ -40366,6 +40438,7 @@
field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE";
field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
+ field public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB = "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
@@ -41718,7 +41791,7 @@
field public static final String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
field public static final String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
- field public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
+ field @Deprecated public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
@@ -41859,6 +41932,7 @@
field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
+ field public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
field public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call";
field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
@@ -43974,6 +44048,8 @@
field public static final int APPTYPE_USIM = 2; // 0x2
field public static final int AUTHTYPE_EAP_AKA = 129; // 0x81
field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
+ field public static final int AUTHTYPE_GBA_BOOTSTRAP = 132; // 0x84
+ field public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL = 133; // 0x85
field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -44056,7 +44132,6 @@
field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L
field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L
field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L
- field public static final long NETWORK_TYPE_BITMASK_IDEN = 1024L; // 0x400L
field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
field @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
@@ -44076,7 +44151,7 @@
field public static final int NETWORK_TYPE_HSPA = 10; // 0xa
field public static final int NETWORK_TYPE_HSPAP = 15; // 0xf
field public static final int NETWORK_TYPE_HSUPA = 9; // 0x9
- field public static final int NETWORK_TYPE_IDEN = 11; // 0xb
+ field @Deprecated public static final int NETWORK_TYPE_IDEN = 11; // 0xb
field public static final int NETWORK_TYPE_IWLAN = 18; // 0x12
field public static final int NETWORK_TYPE_LTE = 13; // 0xd
field public static final int NETWORK_TYPE_NR = 20; // 0x14
@@ -44092,10 +44167,10 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; // 0x3
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; // 0x7
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8; // 0x8
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13; // 0xd
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
- field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
- field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14; // 0xe
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 14; // 0xe
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
@@ -49913,6 +49988,7 @@
public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method public int describeContents();
+ method @NonNull public android.view.SurfaceControl getSurfaceControl();
method public void notifyConfigurationChanged(@NonNull android.content.res.Configuration);
method public void notifyDetachedFromWindow();
method public void release();
@@ -51903,6 +51979,7 @@
method public static int statusBars();
method public static int systemBars();
method public static int systemGestures();
+ method public static int systemOverlays();
method public static int tappableElement();
}
@@ -52339,6 +52416,7 @@
method public CharSequence getClassName();
method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
method public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo getCollectionItemInfo();
+ method @Nullable public CharSequence getContainerTitle();
method public CharSequence getContentDescription();
method public int getDrawingOrder();
method public CharSequence getError();
@@ -52350,6 +52428,7 @@
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
method public int getLiveRegion();
method public int getMaxTextLength();
+ method public int getMinMillisBetweenContentChanges();
method public int getMovementGranularities();
method public CharSequence getPackageName();
method @Nullable public CharSequence getPaneTitle();
@@ -52415,6 +52494,7 @@
method public void setClickable(boolean);
method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo);
+ method public void setContainerTitle(@Nullable CharSequence);
method public void setContentDescription(CharSequence);
method public void setContentInvalid(boolean);
method public void setContextClickable(boolean);
@@ -52436,6 +52516,7 @@
method public void setLiveRegion(int);
method public void setLongClickable(boolean);
method public void setMaxTextLength(int);
+ method public void setMinMillisBetweenContentChanges(int);
method public void setMovementGranularities(int);
method public void setMultiLine(boolean);
method public void setPackageName(CharSequence);
@@ -52515,11 +52596,13 @@
field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
field public static final int FOCUS_INPUT = 1; // 0x1
field public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50; // 0x32
+ field public static final int MINIMUM_MIN_MILLIS_BETWEEN_CONTENT_CHANGES = 100; // 0x64
field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4
field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10
field public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 8; // 0x8
field public static final int MOVEMENT_GRANULARITY_WORD = 2; // 0x2
+ field public static final int UNDEFINED_MIN_MILLIS_BETWEEN_CONTENT_CHANGES = -1; // 0xffffffff
}
public static final class AccessibilityNodeInfo.AccessibilityAction implements android.os.Parcelable {
@@ -53850,6 +53933,7 @@
method @NonNull public String getLanguageTag();
method @Deprecated @NonNull public String getLocale();
method public String getMode();
+ method @NonNull public CharSequence getNameOverride();
method public int getNameResId();
method public boolean isAsciiCapable();
method public boolean isAuxiliary();
@@ -53870,6 +53954,7 @@
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeId(int);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeLocale(String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeMode(String);
+ method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameOverride(@NonNull CharSequence);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
}
@@ -53967,23 +54052,22 @@
}
public final class TextAppearanceInfo implements android.os.Parcelable {
- ctor public TextAppearanceInfo(@NonNull android.widget.TextView);
method public int describeContents();
- method @Nullable public String getFontFamilyName();
method @Nullable public String getFontFeatureSettings();
method @Nullable public String getFontVariationSettings();
+ method @ColorInt public int getHighlightTextColor();
+ method @ColorInt public int getHintTextColor();
method public float getLetterSpacing();
method public int getLineBreakStyle();
method public int getLineBreakWordStyle();
- method public int getMaxLength();
+ method @ColorInt public int getLinkTextColor();
+ method @ColorInt public int getShadowColor();
method @Px public float getShadowDx();
method @Px public float getShadowDy();
method @Px public float getShadowRadius();
+ method @Nullable public String getSystemFontFamilyName();
method @ColorInt public int getTextColor();
- method @ColorInt public int getTextColorHighlight();
- method @ColorInt public int getTextColorHint();
- method @Nullable public android.content.res.ColorStateList getTextColorLink();
- method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
+ method @IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
method @NonNull public android.os.LocaleList getTextLocales();
method public float getTextScaleX();
method @Px public float getTextSize();
@@ -53995,6 +54079,33 @@
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR;
}
+ public static final class TextAppearanceInfo.Builder {
+ ctor public TextAppearanceInfo.Builder();
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo build();
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setAllCaps(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setElegantTextHeight(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFallbackLineSpacing(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontFeatureSettings(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontVariationSettings(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHighlightTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHintTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLetterSpacing(float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakStyle(int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakWordStyle(int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLinkTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDx(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDy(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowRadius(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setSystemFontFamilyName(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextFontWeight(@IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextLocales(@NonNull android.os.LocaleList);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextScaleX(float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextSize(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextStyle(int);
+ }
+
public final class TextAttribute implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.os.PersistableBundle getExtras();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index ce18745..07af1d5 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -297,6 +297,14 @@
package android.os {
+ public class ArtModuleServiceManager {
+ method @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdServiceRegisterer();
+ }
+
+ public static final class ArtModuleServiceManager.ServiceRegisterer {
+ method @Nullable public android.os.IBinder waitForService();
+ }
+
public final class BatteryStatsManager {
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void reportNetworkInterfaceForTransports(@NonNull String, @NonNull int[]) throws java.lang.RuntimeException;
}
@@ -349,6 +357,7 @@
}
public final class ServiceManager {
+ method @NonNull public static String[] getDeclaredInstances(@NonNull String);
method public static boolean isDeclared(@NonNull String);
method @Nullable public static android.os.IBinder waitForDeclaredService(@NonNull String);
method @Nullable public static android.os.IBinder waitForService(@NonNull String);
@@ -384,6 +393,7 @@
method public static void traceBegin(long, @NonNull String);
method public static void traceCounter(long, @NonNull String, int);
method public static void traceEnd(long);
+ field public static final long TRACE_TAG_AIDL = 16777216L; // 0x1000000L
field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L
}
@@ -411,6 +421,7 @@
public final class DeviceConfig {
field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
+ field public static final String NAMESPACE_APP_CLONING = "app_cloning";
field public static final String NAMESPACE_APP_STANDBY = "app_standby";
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f0cf471..baf1f31 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -512,6 +512,10 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean startProfile(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean stopProfile(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull android.os.UserHandle);
+ field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2
+ field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
+ field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4
+ field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
}
public static interface ActivityManager.OnUidImportanceListener {
@@ -588,6 +592,7 @@
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+ field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
@@ -1108,6 +1113,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -1133,6 +1139,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
@@ -1154,6 +1161,7 @@
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER";
+ field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -2908,6 +2916,7 @@
method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
method public int getDefaultActivityPolicy();
method public int getDefaultNavigationPolicy();
+ method public int getDevicePolicy(int);
method public int getLockState();
method @Nullable public String getName();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
@@ -2915,14 +2924,18 @@
field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
+ field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
+ field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
}
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
@@ -3501,6 +3514,7 @@
field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
+ field public static final String FEATURE_VIRTUALIZATION_FRAMEWORK = "android.software.virtualization_framework";
field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000
@@ -3615,6 +3629,7 @@
field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+ field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
field @Deprecated public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
field @Nullable public final String backgroundPermission;
field @NonNull public java.util.Set<java.lang.String> knownCerts;
@@ -7155,6 +7170,7 @@
public class Tuner implements java.lang.AutoCloseable {
ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int);
+ method public int applyFrontend(@NonNull android.media.tv.tuner.frontend.FrontendInfo);
method public int cancelScanning();
method public int cancelTuning();
method public void clearOnTuneEventListener();
@@ -7185,7 +7201,6 @@
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);
method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
method public int removeOutputPid(@IntRange(from=0) int);
- method public int requestFrontendById(int);
method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
method public int setLnaEnabled(boolean);
method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
@@ -9968,9 +9983,10 @@
method public boolean isCloneProfile();
method public boolean isCredentialSharableWithParent();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isMainUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
method public boolean isMediaSharedWithParent();
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
method public static boolean isRemoveResultSuccessful(int);
method public boolean isRestrictedProfile();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
@@ -11924,11 +11940,39 @@
method public abstract void onStopUpdates();
method public final void reportPermanentFailure(@NonNull Throwable);
method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion);
+ method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion, @NonNull android.service.timezone.TimeZoneProviderStatus);
method public final void reportUncertain();
+ method public final void reportUncertain(@NonNull android.service.timezone.TimeZoneProviderStatus);
field public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.PrimaryLocationTimeZoneProviderService";
field public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.SecondaryLocationTimeZoneProviderService";
}
+ public final class TimeZoneProviderStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConnectivityDependencyStatus();
+ method public int getLocationDetectionDependencyStatus();
+ method public int getTimeZoneResolutionOperationStatus();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.timezone.TimeZoneProviderStatus> CREATOR;
+ field public static final int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4; // 0x4
+ field public static final int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6; // 0x6
+ field public static final int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5; // 0x5
+ field public static final int DEPENDENCY_STATUS_NOT_APPLICABLE = 1; // 0x1
+ field public static final int DEPENDENCY_STATUS_OK = 2; // 0x2
+ field public static final int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+ field public static final int OPERATION_STATUS_FAILED = 3; // 0x3
+ field public static final int OPERATION_STATUS_NOT_APPLICABLE = 1; // 0x1
+ field public static final int OPERATION_STATUS_OK = 2; // 0x2
+ }
+
+ public static final class TimeZoneProviderStatus.Builder {
+ ctor public TimeZoneProviderStatus.Builder();
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus build();
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setConnectivityDependencyStatus(int);
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setLocationDetectionDependencyStatus(int);
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setTimeZoneResolutionOperationStatus(int);
+ }
+
public final class TimeZoneProviderSuggestion implements android.os.Parcelable {
method public int describeContents();
method public long getElapsedRealtimeMillis();
@@ -12120,6 +12164,7 @@
method public static int getMaxScore();
method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
method public int getPersonalizedScore();
+ method public int getProximity();
method public int getScore();
method public boolean isHotwordDetectionPersonalized();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -12133,6 +12178,9 @@
field public static final int CONFIDENCE_LEVEL_VERY_HIGH = 6; // 0x6
field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordDetectedResult> CREATOR;
field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
+ field public static final int PROXIMITY_FAR = 2; // 0x2
+ field public static final int PROXIMITY_NEAR = 1; // 0x1
+ field public static final int PROXIMITY_UNKNOWN = -1; // 0xffffffff
}
public static final class HotwordDetectedResult.Builder {
@@ -12221,7 +12269,7 @@
public class WallpaperService.Engine {
method public boolean isInAmbientMode();
- method public void onAmbientModeChanged(boolean, long);
+ method @MainThread public void onAmbientModeChanged(boolean, long);
}
}
@@ -12469,6 +12517,8 @@
field public static final int CS_BOUND = 6; // 0x6
field public static final int DIRECT_TO_VM_FINISHED = 103; // 0x67
field public static final int DIRECT_TO_VM_INITIATED = 102; // 0x66
+ field public static final int DND_CHECK_COMPLETED = 110; // 0x6e
+ field public static final int DND_CHECK_INITIATED = 109; // 0x6d
field public static final int FILTERING_COMPLETED = 107; // 0x6b
field public static final int FILTERING_INITIATED = 106; // 0x6a
field public static final int FILTERING_TIMED_OUT = 108; // 0x6c
@@ -12508,6 +12558,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics.EventTiming> CREATOR;
field public static final int DIRECT_TO_VM_FINISHED_TIMING = 8; // 0x8
field public static final int DISCONNECT_TIMING = 2; // 0x2
+ field public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12; // 0xc
field public static final int FILTERING_COMPLETED_TIMING = 10; // 0xa
field public static final int FILTERING_TIMED_OUT_TIMING = 11; // 0xb
field public static final int HOLD_TIMING = 3; // 0x3
@@ -14155,6 +14206,7 @@
ctor public QualifiedNetworksService.NetworkAvailabilityProvider(int);
method public abstract void close();
method public final int getSlotIndex();
+ method public void reportEmergencyDataNetworkPreferredTransportChanged(int);
method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>);
method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
}
@@ -14237,7 +14289,8 @@
field public static final int RESULT_CALLER_NOT_ALLOWED = -3; // 0xfffffffd
field public static final int RESULT_EUICC_NOT_FOUND = -2; // 0xfffffffe
field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1
+ field public static final int RESULT_PROFILE_DOES_NOT_EXIST = -4; // 0xfffffffc
+ field @Deprecated public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1
field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f076395..85d73ec 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -143,11 +143,7 @@
field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf
field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
- field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2
- field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
- field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4
field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
- field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
field public static final int PROCESS_STATE_TOP = 2; // 0x2
field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
@@ -758,6 +754,7 @@
method public int getUserId();
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+ field public static final String ATTENTION_SERVICE = "attention";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
field public static final String DREAM_SERVICE = "dream";
@@ -862,10 +859,6 @@
field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
}
- public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
- field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
- }
-
public final class ProviderInfoList implements android.os.Parcelable {
method public int describeContents();
method @NonNull public static android.content.pm.ProviderInfoList fromList(@NonNull java.util.List<android.content.pm.ProviderInfo>);
@@ -904,6 +897,7 @@
method public boolean isFull();
method public boolean isGuest();
method public boolean isInitialized();
+ method public boolean isMain();
method public boolean isManagedProfile();
method public boolean isPrimary();
method public boolean isProfile();
@@ -922,6 +916,7 @@
field public static final int FLAG_FULL = 1024; // 0x400
field @Deprecated public static final int FLAG_GUEST = 4; // 0x4
field public static final int FLAG_INITIALIZED = 16; // 0x10
+ field public static final int FLAG_MAIN = 16384; // 0x4000
field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20
field public static final int FLAG_PRIMARY = 1; // 0x1
field public static final int FLAG_PROFILE = 4096; // 0x1000
@@ -1711,6 +1706,7 @@
public class Build {
method public static boolean is64BitAbi(String);
method public static boolean isDebuggable();
+ method public static boolean isSecure();
field public static final boolean IS_EMULATOR;
}
@@ -1899,6 +1895,7 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method public static boolean isSplitSystemUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
method public boolean isUsersOnSecondaryDisplaysSupported();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
@@ -2498,6 +2495,10 @@
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
}
+ public abstract class HotwordDetectionService extends android.app.Service {
+ field public static final boolean ENABLE_PROXIMITY_RESULT = true;
+ }
+
public final class VisibleActivityInfo implements android.os.Parcelable {
ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
}
@@ -2621,13 +2622,23 @@
method public int getCarrierIdListVersion();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCertsFromCarrierPrivilegeAccessRules();
method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
+ method @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getHalVersion(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
- method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
+ method @Deprecated public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+ field public static final int HAL_SERVICE_DATA = 1; // 0x1
+ field public static final int HAL_SERVICE_IMS = 7; // 0x7
+ field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
+ field public static final int HAL_SERVICE_MODEM = 3; // 0x3
+ field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+ field public static final int HAL_SERVICE_SIM = 5; // 0x5
+ field public static final int HAL_SERVICE_VOICE = 6; // 0x6
+ field public static final android.util.Pair HAL_VERSION_UNKNOWN;
+ field public static final android.util.Pair HAL_VERSION_UNSUPPORTED;
field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
}
@@ -2944,6 +2955,7 @@
method public static int getHoverTooltipHideTimeout();
method public static int getHoverTooltipShowTimeout();
method public static int getLongPressTooltipHideTimeout();
+ method public static long getSendRecurringAccessibilityEventsInterval();
method public boolean isPreferKeepClearForFocusEnabled();
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index f9b8a30..8e21d8c 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -567,6 +567,10 @@
Missing nullability on parameter `destAddress` in method `checkSmsShortCodeDestination`
MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(String, String) parameter #1:
Missing nullability on parameter `countryIso` in method `checkSmsShortCodeDestination`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNKNOWN:
+ Missing nullability on field `HAL_VERSION_UNKNOWN` in class `class android.telephony.TelephonyManager`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNSUPPORTED:
+ Missing nullability on field `HAL_VERSION_UNSUPPORTED` in class `class android.telephony.TelephonyManager`
MissingNullability: android.telephony.TelephonyManager#getLine1AlphaTag():
Missing nullability on method `getLine1AlphaTag` return
MissingNullability: android.telephony.TelephonyManager#getRadioHalVersion():
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e86d2f3..02a81ac 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -56,7 +56,7 @@
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.SurfaceView;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityCache;
@@ -696,7 +696,8 @@
ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
- ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
+ ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+ ERROR_TAKE_SCREENSHOT_INVALID_WINDOW
})
public @interface ScreenshotErrorCode {}
@@ -728,6 +729,18 @@
public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
/**
+ * The status of taking screenshot is failure and the reason is invalid accessibility window Id.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5;
+
+ /**
+ * The status of taking screenshot is failure and the reason is the window contains secure
+ * content.
+ * @see WindowManager.LayoutParams#FLAG_SECURE
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6;
+
+ /**
* The interval time of calling
* {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
* @hide
@@ -2454,8 +2467,8 @@
* </p>
* <p>
* <strong>Note:</strong> If the view with {@link AccessibilityNodeInfo#FOCUS_INPUT}
- * is on an embedded view hierarchy which is embedded in a {@link SurfaceView} via
- * {@link SurfaceView#setChildSurfacePackage}, there is a limitation that this API
+ * is on an embedded view hierarchy which is embedded in a {@link android.view.SurfaceView} via
+ * {@link android.view.SurfaceView#setChildSurfacePackage}, there is a limitation that this API
* won't be able to find the node for the view. It's because views don't know about
* the embedded hierarchies. Instead, you could traverse all the nodes to find the
* focus.
@@ -2568,6 +2581,7 @@
* @param executor Executor on which to run the callback.
* @param callback The callback invoked when taking screenshot has succeeded or failed.
* See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshotOfWindow
*/
public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
@NonNull TakeScreenshotCallback callback) {
@@ -2589,7 +2603,8 @@
final HardwareBuffer hardwareBuffer =
result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, android.hardware.HardwareBuffer.class);
final ParcelableColorSpace colorSpace =
- result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, android.graphics.ParcelableColorSpace.class);
+ result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE,
+ android.graphics.ParcelableColorSpace.class);
final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
colorSpace.getColorSpace(),
result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
@@ -2601,6 +2616,37 @@
}
/**
+ * Takes a screenshot of the specified window and returns it via an
+ * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+ * to construct the bitmap from the ScreenshotResult's payload.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshots your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+ * screen understanding. Use <code>takeScreenshotOfWindow</code> if your target window might be
+ * visually underneath an accessibility overlay (from your or another accessibility service) in
+ * order to capture the window contents without the screenshot being covered by the overlay
+ * contents drawn on the screen.
+ * </p>
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when taking screenshot has succeeded or failed.
+ * See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshot
+ */
+ public void takeScreenshotOfWindow(int accessibilityWindowId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull TakeScreenshotCallback callback) {
+ AccessibilityInteractionClient.getInstance(this).takeScreenshotOfWindow(
+ mConnectionId, accessibilityWindowId, executor, callback);
+ }
+
+ /**
* Sets the strokeWidth and color of the accessibility focus rectangle.
* <p>
* <strong>Note:</strong> This setting persists until this or another active
@@ -3113,7 +3159,8 @@
private final @NonNull ColorSpace mColorSpace;
private final long mTimestamp;
- private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+ /** @hide */
+ public ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
@NonNull ColorSpace colorSpace, long timestamp) {
Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
@@ -3332,4 +3379,28 @@
controller.onStateChanged(state);
}
}
+
+ /**
+ * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
+ * specified display. This type of overlay should be used for content that does not need to
+ * track the location and size of Views in the currently active app e.g. service configuration
+ * or general service UI. To remove this overlay and free the associated resources, use
+ * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+ *
+ * @param displayId the display to which the SurfaceControl should be attached.
+ * @param sc the SurfaceControl containing the overlay content
+ */
+ public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getConnection(mConnectionId);
+ if (connection == null) {
+ return;
+ }
+ try {
+ connection.attachAccessibilityOverlayToDisplay(displayId, sc);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 9abce3a..da13e73 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -25,11 +25,13 @@
import android.os.RemoteCallback;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.accessibility.AccessibilityWindowInfo;
import java.util.List;
+import android.window.ScreenCapture;
/**
* Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -122,6 +124,10 @@
void takeScreenshot(int displayId, in RemoteCallback callback);
+ void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+ in ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback);
+
void setGestureDetectionPassthroughRegion(int displayId, in Region region);
void setTouchExplorationPassthroughRegion(int displayId, in Region region);
@@ -150,4 +156,5 @@
void setInstalledAndEnabledServices(in List<AccessibilityServiceInfo> infos);
List<AccessibilityServiceInfo> getInstalledAndEnabledServices();
+ void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl sc);
}
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 1403ba2..df8a50a 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -19,10 +19,10 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
import android.view.Choreographer;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -78,7 +78,7 @@
* store visible (foreground) requestors; if the set size reaches zero, there are no
* objects in the foreground and it is time to pause animators.
*/
- private final ArraySet<Object> mAnimatorRequestors = new ArraySet<>();
+ private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
@@ -141,19 +141,9 @@
* tracking obsolete+enabled requestors.
*/
public static void removeRequestor(Object requestor) {
- getInstance().removeRequestorImpl(requestor);
- }
-
- private void removeRequestorImpl(Object requestor) {
- // Also request disablement, in case that requestor was the sole object keeping
- // animators un-paused
- requestAnimatorsEnabled(false, requestor);
- mAnimatorRequestors.remove(requestor);
+ getInstance().requestAnimatorsEnabledImpl(false, requestor);
if (LOCAL_LOGV) {
- Log.v(TAG, "removeRequestorImpl for " + requestor);
- for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
- Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
- }
+ Log.v(TAG, "removeRequestor for " + requestor);
}
}
@@ -173,10 +163,36 @@
private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) {
boolean wasEmpty = mAnimatorRequestors.isEmpty();
setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
- if (enable) {
- mAnimatorRequestors.add(requestor);
- } else {
- mAnimatorRequestors.remove(requestor);
+ synchronized (mAnimatorRequestors) {
+ // Only store WeakRef objects to avoid leaks
+ if (enable) {
+ // First, check whether such a reference is already on the list
+ WeakReference<Object> weakRef = null;
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor) {
+ weakRef = ref;
+ } else if (referent == null) {
+ // Remove any reference that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ if (weakRef == null) {
+ weakRef = new WeakReference<>(requestor);
+ mAnimatorRequestors.add(weakRef);
+ }
+ } else {
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor || referent == null) {
+ // remove requested item or item that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ // If a reference to the requestor wasn't in the list, nothing to remove
+ }
}
if (!sAnimatorPausingEnabled) {
// Resume any animators that have been paused in the meantime, otherwise noop
@@ -198,9 +214,12 @@
}
}
if (LOCAL_LOGV) {
- Log.v(TAG, enable ? "enable" : "disable" + " animators for " + requestor);
+ Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor
+ + " with pauseDelay of " + Animator.getBackgroundPauseDelay());
for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
- Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
+ Log.v(TAG, "animatorRequestors " + i + " = "
+ + mAnimatorRequestors.get(i) + " with referent "
+ + mAnimatorRequestors.get(i).get());
}
}
}
@@ -219,12 +238,14 @@
return;
}
for (int i = 0; i < mAnimationCallbacks.size(); ++i) {
- Animator animator = ((Animator) mAnimationCallbacks.get(i));
- if (animator != null
- && animator.getTotalDuration() == Animator.DURATION_INFINITE
- && !animator.isPaused()) {
- mPausedAnimators.add(animator);
- animator.pause();
+ AnimationFrameCallback callback = mAnimationCallbacks.get(i);
+ if (callback instanceof Animator) {
+ Animator animator = ((Animator) callback);
+ if (animator.getTotalDuration() == Animator.DURATION_INFINITE
+ && !animator.isPaused()) {
+ mPausedAnimators.add(animator);
+ animator.pause();
+ }
}
}
};
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 501b136..30ff052 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1685,7 +1685,7 @@
.isOnBackInvokedCallbackEnabled(this);
if (aheadOfTimeBack) {
// Add onBackPressed as default back behavior.
- mDefaultBackCallback = this::navigateBack;
+ mDefaultBackCallback = this::onBackInvoked;
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
}
}
@@ -4003,22 +4003,19 @@
if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
return;
}
- navigateBack();
+ onBackInvoked();
}
- private void navigateBack() {
- if (!isTaskRoot()) {
- // If the activity is not the root of the task, allow finish to proceed normally.
- finishAfterTransition();
- return;
- }
- // Inform activity task manager that the activity received a back press while at the
- // root of the task. This call allows ActivityTaskManager to intercept or move the task
- // to the back.
- ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken,
+ private void onBackInvoked() {
+ // Inform activity task manager that the activity received a back press.
+ // This call allows ActivityTaskManager to intercept or move the task
+ // to the back when needed.
+ ActivityClient.getInstance().onBackPressed(mToken,
new RequestFinishCallback(new WeakReference<>(this)));
- getAutofillClientController().onActivityBackPressed(mIntent);
+ if (isTaskRoot()) {
+ getAutofillClientController().onActivityBackPressed(mIntent);
+ }
}
/**
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index d1e6780..4cf48ab 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -525,9 +525,9 @@
}
}
- void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+ void onBackPressed(IBinder token, IRequestFinishCallback callback) {
try {
- getActivityClientController().onBackPressedOnTaskRoot(token, callback);
+ getActivityClientController().onBackPressed(token, callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d6c10ae..83963fd 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -628,19 +628,19 @@
public @interface ProcessCapability {}
/** @hide Process does not have any capability */
- @TestApi
+ @SystemApi
public static final int PROCESS_CAPABILITY_NONE = 0;
/** @hide Process can access location while in foreground */
- @TestApi
+ @SystemApi
public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 0;
/** @hide Process can access camera while in foreground */
- @TestApi
+ @SystemApi
public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 1;
/** @hide Process can access microphone while in foreground */
- @TestApi
+ @SystemApi
public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
/** @hide Process can access network despite any power saving resrictions */
@@ -4398,8 +4398,6 @@
*
* @throws UnsupportedOperationException if the device does not support background users on
* secondary displays.
- * @throws IllegalArgumentException if the display doesn't exist or is not a valid display to
- * start secondary users on.
*
* @hide
*/
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index baa3bd9..f62190a 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -577,13 +577,6 @@
public abstract void stopAppForUser(String pkg, @UserIdInt int userId);
/**
- * If the given app has any FGSs whose notifications are in the given channel,
- * stop them.
- */
- public abstract void stopForegroundServicesForChannel(String pkg, @UserIdInt int userId,
- String channelId);
-
- /**
* Registers the specified {@code processObserver} to be notified of future changes to
* process state.
*/
@@ -649,6 +642,8 @@
* using the rules of package visibility. Returns extras with legitimate package info that the
* receiver is able to access, or {@code null} if none of the packages is visible to the
* receiver.
+ * @param serialized Specifies whether or not the broadcast should be delivered to the
+ * receivers in a serial order.
*
* @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
* IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
@@ -662,6 +657,19 @@
@Nullable Bundle bOptions);
/**
+ * Variant of
+ * {@link #broadcastIntent(Intent, IIntentReceiver, String[], boolean, int, int[], BiFunction, Bundle)}
+ * that allows sender to receive a finish callback once the broadcast delivery is completed,
+ * but provides no ordering guarantee for how the broadcast is delivered to receivers.
+ */
+ public abstract int broadcastIntentWithCallback(Intent intent,
+ IIntentReceiver resultTo,
+ String[] requiredPermissions,
+ int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions);
+
+ /**
* Add uid to the ActivityManagerService PendingStartActivityUids list.
* @param uid uid
* @param pid pid of the ProcessRecord that is pending top.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1f63343..e001c7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,7 +40,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
@@ -1994,9 +1993,6 @@
case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
throw generateForegroundServiceDidNotStartInTimeException(message, extras);
- case CannotDeliverBroadcastException.TYPE_ID:
- throw new CannotDeliverBroadcastException(message);
-
case CannotPostForegroundServiceNotificationException.TYPE_ID:
throw new CannotPostForegroundServiceNotificationException(message);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index dc325ff..d5879fb 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -42,6 +42,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.database.DatabaseUtils;
+import android.healthconnect.HealthConnectManager;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
import android.os.Build;
@@ -1390,9 +1391,57 @@
public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
+ /**
+ * An app op for reading/writing health connect data.
+ *
+ * @hide
+ */
+ public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+
+ /**
+ * Use foreground service with the type
+ * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+ *
+ * @hide
+ */
+ public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
+ AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+
+ /**
+ * Exempt from start foreground service from background restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION;
+
+ /**
+ * Exempt from start foreground service from background with while in user permission
+ * restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+ AppProtoEnums
+ .APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION;
+
+ /**
+ * Hide foreground service stop button in quick settings.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 126;
+ public static final int _NUM_OP = 131;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1873,6 +1922,15 @@
"android:read_media_visual_user_selected";
/**
+ * An app op for reading/writing health connect data.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String OPSTR_READ_WRITE_HEALTH_DATA =
+ "android:read_write_health_data";
+
+ /**
* Record audio from near-field microphone (ie. TV remote)
* Allows audio recording regardless of sensor privacy state,
* as it is an intentional user interaction: hold-to-talk
@@ -1913,6 +1971,46 @@
public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
"android:system_exempt_from_forced_app_standby";
+ /**
+ * Start a foreground service with the type "specialUse".
+ *
+ * @hide
+ */
+ public static final String OPSTR_FOREGROUND_SERVICE_SPECIAL_USE =
+ "android:foreground_service_special_use";
+
+ /**
+ * Exempt from start foreground service from background restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+ "android:system_exempt_from_fgs_bg_start_restriction";
+
+ /**
+ * Exempt from start foreground service from background with while in user permission
+ * restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+ "android:system_exempt_from_fgs_bg_start_while_in_use_permission_restriction";
+
+ /**
+ * Hide foreground service stop button in quick settings.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+ "android:system_exempt_from_fgs_stop_button";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2009,6 +2107,7 @@
OP_TURN_SCREEN_ON,
OP_RUN_LONG_JOBS,
OP_READ_MEDIA_VISUAL_USER_SELECTED,
+ OP_FOREGROUND_SERVICE_SPECIAL_USE,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2400,9 +2499,28 @@
"SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
- "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
+ "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build(),
+ new AppOpInfo.Builder(OP_READ_WRITE_HEALTH_DATA, OPSTR_READ_WRITE_HEALTH_DATA,
+ "READ_WRITE_HEALTH_DATA").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_FOREGROUND_SERVICE_SPECIAL_USE,
+ OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, "FOREGROUND_SERVICE_SPECIAL_USE")
+ .setPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE).build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+ "SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION").build(),
+ new AppOpInfo.Builder(
+ OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+ "SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION")
+ .build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+ "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build()
};
+ // The number of longs needed to form a full bitmask of app ops
+ private static final int BITMASK_LEN = ((_NUM_OP - 1) / Long.SIZE) + 1;
+
/**
* @hide
*/
@@ -2437,8 +2555,8 @@
* @see #getNotedOpCollectionMode
* @see #collectNotedOpSync
*/
- private static final ThreadLocal<ArrayMap<String, long[]>> sAppOpsNotedInThisBinderTransaction =
- new ThreadLocal<>();
+ private static final ThreadLocal<ArrayMap<String, BitSet>>
+ sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>();
static {
if (sAppOpInfos.length != _NUM_OP) {
@@ -2455,12 +2573,6 @@
sPermToOp.put(sAppOpInfos[op].permission, op);
}
}
-
- if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
- // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
- // two longs
- throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
- }
}
/** Config used to control app ops access messages sampling */
@@ -2557,7 +2669,14 @@
@TestApi
public static int permissionToOpCode(String permission) {
Integer boxedOpCode = sPermToOp.get(permission);
- return boxedOpCode != null ? boxedOpCode : OP_NONE;
+ if (boxedOpCode != null) {
+ return boxedOpCode;
+ }
+ if (permission != null && HealthConnectManager.isHealthPermission(
+ ActivityThread.currentApplication(), permission)) {
+ return OP_READ_WRITE_HEALTH_DATA;
+ }
+ return OP_NONE;
}
/**
@@ -7221,10 +7340,14 @@
*/
public static @Nullable String permissionToOp(@NonNull String permission) {
final Integer opCode = sPermToOp.get(permission);
- if (opCode == null) {
- return null;
+ if (opCode != null) {
+ return sAppOpInfos[opCode].name;
}
- return sAppOpInfos[opCode].name;
+ if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
+ permission)) {
+ return sAppOpInfos[OP_READ_WRITE_HEALTH_DATA].name;
+ }
+ return null;
}
/**
@@ -8453,8 +8576,9 @@
*/
public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean skipProxyOperation) {
- return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation,
- ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+ return startProxyOpNoThrow(attributionSource.getToken(), op, attributionSource, message,
+ skipProxyOperation, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE,
+ ATTRIBUTION_CHAIN_ID_NONE);
}
/**
@@ -8466,7 +8590,8 @@
*
* @hide
*/
- public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+ public int startProxyOpNoThrow(@NonNull IBinder clientId, int op,
+ @NonNull AttributionSource attributionSource,
@Nullable String message, boolean skipProxyOperation, @AttributionFlags
int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
int attributionChainId) {
@@ -8484,7 +8609,7 @@
}
}
- SyncNotedAppOp syncOp = mService.startProxyOperation(op,
+ SyncNotedAppOp syncOp = mService.startProxyOperation(clientId, op,
attributionSource, false, collectionMode == COLLECT_ASYNC, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
@@ -8582,9 +8707,10 @@
*/
public void finishProxyOp(@NonNull String op, int proxiedUid,
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
- finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
+ IBinder token = mContext.getAttributionSource().getToken();
+ finishProxyOp(token, op, new AttributionSource(mContext.getAttributionSource(),
new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
+ token)), /*skipProxyOperation*/ false);
}
/**
@@ -8599,10 +8725,11 @@
*
* @hide
*/
- public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation) {
+ public void finishProxyOp(@NonNull IBinder clientId, @NonNull String op,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
try {
- mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation);
+ mService.finishProxyOperation(clientId, strOpToOp(op), attributionSource,
+ skipProxyOperation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8684,10 +8811,10 @@
*/
public static class PausedNotedAppOpsCollection {
final int mUid;
- final @Nullable ArrayMap<String, long[]> mCollectedNotedAppOps;
+ final @Nullable ArrayMap<String, BitSet> mCollectedNotedAppOps;
PausedNotedAppOpsCollection(int uid, @Nullable ArrayMap<String,
- long[]> collectedNotedAppOps) {
+ BitSet> collectedNotedAppOps) {
mUid = uid;
mCollectedNotedAppOps = collectedNotedAppOps;
}
@@ -8705,7 +8832,7 @@
public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
Integer previousUid = sBinderThreadCallingUid.get();
if (previousUid != null) {
- ArrayMap<String, long[]> previousCollectedNotedAppOps =
+ ArrayMap<String, BitSet> previousCollectedNotedAppOps =
sAppOpsNotedInThisBinderTransaction.get();
sBinderThreadCallingUid.remove();
@@ -8779,23 +8906,19 @@
// We are inside of a two-way binder call. Delivered to caller via
// {@link #prefixParcelWithAppOpsIfNeeded}
int op = sOpStrToOp.get(syncOp.getOp());
- ArrayMap<String, long[]> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
+ ArrayMap<String, BitSet> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
if (appOpsNoted == null) {
appOpsNoted = new ArrayMap<>(1);
sAppOpsNotedInThisBinderTransaction.set(appOpsNoted);
}
- long[] appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
+ BitSet appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
if (appOpsNotedForAttribution == null) {
- appOpsNotedForAttribution = new long[2];
+ appOpsNotedForAttribution = new BitSet(_NUM_OP);
appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution);
}
- if (op < 64) {
- appOpsNotedForAttribution[0] |= 1L << op;
- } else {
- appOpsNotedForAttribution[1] |= 1L << (op - 64);
- }
+ appOpsNotedForAttribution.set(op);
}
/** @hide */
@@ -8869,7 +8992,7 @@
*/
// TODO (b/186872903) Refactor how sync noted ops are propagated.
public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
- ArrayMap<String, long[]> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+ ArrayMap<String, BitSet> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
if (notedAppOps == null) {
return;
}
@@ -8881,8 +9004,15 @@
for (int i = 0; i < numAttributionWithNotesAppOps; i++) {
p.writeString(notedAppOps.keyAt(i));
- p.writeLong(notedAppOps.valueAt(i)[0]);
- p.writeLong(notedAppOps.valueAt(i)[1]);
+ // Bitmask's toLongArray will truncate the array, if upper bits arent used
+ long[] notedOpsMask = notedAppOps.valueAt(i).toLongArray();
+ for (int j = 0; j < BITMASK_LEN; j++) {
+ if (j < notedOpsMask.length) {
+ p.writeLong(notedOpsMask[j]);
+ } else {
+ p.writeLong(0);
+ }
+ }
}
}
@@ -8901,12 +9031,13 @@
for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
String attributionTag = p.readString();
- long[] rawNotedAppOps = new long[2];
- rawNotedAppOps[0] = p.readLong();
- rawNotedAppOps[1] = p.readLong();
+ long[] rawNotedAppOps = new long[BITMASK_LEN];
+ for (int j = 0; j < rawNotedAppOps.length; j++) {
+ rawNotedAppOps[j] = p.readLong();
+ }
+ BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
- if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
- BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+ if (!notedAppOps.isEmpty()) {
synchronized (sLock) {
for (int code = notedAppOps.nextSetBit(0); code != -1;
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 4d6e4ae..43023fe 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -26,13 +26,11 @@
import android.util.SparseIntArray;
import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintConsumer;
import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
/**
@@ -135,6 +133,7 @@
/**
* Allows overriding start proxy operation behavior.
*
+ * @param clientId The client calling start, represented by an IBinder
* @param code The op code to start.
* @param attributionSource The permission identity of the caller.
* @param startIfModeDefault Whether to start the op of the mode is default.
@@ -148,11 +147,12 @@
* @param superImpl The super implementation.
* @return The app op note result.
*/
- SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
- int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
- int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean,
+ SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+ @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean,
Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
SyncNotedAppOp> superImpl);
@@ -176,10 +176,15 @@
*
* @param code The op code to finish.
* @param attributionSource The permission identity of the caller.
+ * @param skipProxyOperation Whether to skip the proxy in the proxy/proxied operation
+ * @param clientId The client calling finishProxyOperation
+ * @param superImpl The "standard" implementation to potentially call
*/
- void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource,
boolean skipProxyOperation,
- @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl);
+ @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+ Void> superImpl);
}
/**
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 45d4458..1777f37 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -63,7 +63,6 @@
private long mRequireCompatChangeId = CHANGE_INVALID;
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
- private boolean mIsInteractiveBroadcast = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -171,13 +170,6 @@
"android:broadcast.is_alarm";
/**
- * Corresponds to {@link #setInteractiveBroadcast(boolean)}
- * @hide
- */
- public static final String KEY_INTERACTIVE_BROADCAST =
- "android:broadcast.is_interactive";
-
- /**
* @hide
* @deprecated Use {@link android.os.PowerExemptionManager#
* TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -308,7 +300,6 @@
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
- mIsInteractiveBroadcast = opts.getBoolean(KEY_INTERACTIVE_BROADCAST, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
@@ -629,28 +620,6 @@
}
/**
- * When set, this broadcast will be understood as having originated from
- * some direct interaction by the user such as a notification tap or button
- * press. Only the OS itself may use this option.
- * @hide
- * @param broadcastIsInteractive
- * @see #isInteractiveBroadcast()
- */
- @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
- public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
- mIsInteractiveBroadcast = broadcastIsInteractive;
- }
-
- /**
- * Did this broadcast originate with a direct user interaction?
- * @return true if this broadcast is the result of an interaction, false otherwise
- * @hide
- */
- public boolean isInteractiveBroadcast() {
- return mIsInteractiveBroadcast;
- }
-
- /**
* Did this broadcast originate from a push message from the server?
*
* @return true if this broadcast is a push message, false otherwise.
@@ -837,9 +806,6 @@
if (mIsAlarmBroadcast) {
b.putBoolean(KEY_ALARM_BROADCAST, true);
}
- if (mIsInteractiveBroadcast) {
- b.putBoolean(KEY_INTERACTIVE_BROADCAST, true);
- }
if (mMinManifestReceiverApiLevel != 0) {
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 4e5e384..74db39f 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.RequiresPermission;
import android.os.Bundle;
/**
@@ -45,8 +46,15 @@
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
"android.pendingIntent.backgroundActivityAllowedByPermission";
+ /**
+ * Corresponds to {@link #setInteractive(boolean)}
+ * @hide
+ */
+ public static final String KEY_INTERACTIVE = "android:component.isInteractive";
+
private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
private boolean mPendingIntentBalAllowedByPermission = false;
+ private boolean mIsInteractive = false;
ComponentOptions() {
}
@@ -61,6 +69,29 @@
setPendingIntentBackgroundActivityLaunchAllowedByPermission(
opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
false));
+ mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false);
+ }
+
+ /**
+ * When set, a broadcast will be understood as having originated from
+ * some direct interaction by the user such as a notification tap or button
+ * press. Only the OS itself may use this option.
+ * @hide
+ * @param interactive
+ * @see #isInteractive()
+ */
+ @RequiresPermission(android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE)
+ public void setInteractive(boolean interactive) {
+ mIsInteractive = interactive;
+ }
+
+ /**
+ * Did this PendingIntent send originate with a direct user interaction?
+ * @return true if this is the result of an interaction, false otherwise
+ * @hide
+ */
+ public boolean isInteractive() {
+ return mIsInteractive;
}
/**
@@ -103,6 +134,9 @@
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
mPendingIntentBalAllowedByPermission);
}
+ if (mIsInteractive) {
+ b.putBoolean(KEY_INTERACTIVE, true);
+ }
return b;
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 10cdf53..042bdd7 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1814,12 +1814,6 @@
}
}
try {
- ActivityThread thread = ActivityThread.currentActivityThread();
- Instrumentation instrumentation = thread.getInstrumentation();
- if (instrumentation.isInstrumenting()
- && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
- flags = flags | Context.RECEIVER_EXPORTED;
- }
final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index ae3a9e6..fae6887 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -188,7 +188,17 @@
}
@Override
- public WallpaperInfo getWallpaperInfo(int userId) {
+ public WallpaperInfo getWallpaperInfoForUser(int userId) {
+ return unsupported();
+ }
+
+ @Override
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
+ return unsupported();
+ }
+
+ @Override
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
return unsupported();
}
diff --git a/core/java/android/app/ForegroundServiceTypeNotAllowedException.java b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
new file mode 100644
index 0000000..c258242
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.app;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when an app tries to start a foreground {@link Service} without a valid type.
+ */
+public final class ForegroundServiceTypeNotAllowedException
+ extends ServiceStartNotAllowedException implements Parcelable {
+ /**
+ * Constructor.
+ */
+ public ForegroundServiceTypeNotAllowedException(@NonNull String message) {
+ super(message);
+ }
+
+ ForegroundServiceTypeNotAllowedException(@NonNull Parcel source) {
+ super(source.readString());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ }
+
+ public static final @NonNull Creator<android.app.ForegroundServiceTypeNotAllowedException>
+ CREATOR = new Creator<android.app.ForegroundServiceTypeNotAllowedException>() {
+ @NonNull
+ public android.app.ForegroundServiceTypeNotAllowedException createFromParcel(
+ Parcel source) {
+ return new android.app.ForegroundServiceTypeNotAllowedException(source);
+ }
+
+ @NonNull
+ public android.app.ForegroundServiceTypeNotAllowedException[] newArray(int size) {
+ return new android.app.ForegroundServiceTypeNotAllowedException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
new file mode 100644
index 0000000..eccc563
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -0,0 +1,1033 @@
+/*
+ * 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 android.app;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.compat.CompatChanges;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Optional;
+
+/**
+ * This class enforces the policies around the foreground service types.
+ *
+ * @hide
+ */
+public abstract class ForegroundServiceTypePolicy {
+ static final String TAG = "ForegroundServiceTypePolicy";
+ static final boolean DEBUG_FOREGROUND_SERVICE_TYPE_POLICY = false;
+
+ /**
+ * The FGS type enforcement:
+ * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * <p>Starting a FGS with this type (equivalent of no type) from apps with
+ * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in a warning in the log.</p>
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @Overridable
+ public static final long FGS_TYPE_NONE_DEPRECATION_CHANGE_ID = 255042465L;
+
+ /**
+ * The FGS type enforcement:
+ * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * <p>Starting a FGS with this type (equivalent of no type) from apps with
+ * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in an exception.</p>
+ *
+ * @hide
+ */
+ // TODO (b/254661666): Change to @EnabledAfter(T)
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
+
+ /**
+ * The FGS type enforcement:
+ * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+ *
+ * <p>Starting a FGS with this type from apps with targetSdkVersion
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in a warning in the log.</p>
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @Overridable
+ public static final long FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID = 255039210L;
+
+ /**
+ * The FGS type enforcement:
+ * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+ *
+ * <p>Starting a FGS with this type from apps with targetSdkVersion
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in an exception.</p>
+ *
+ * @hide
+ */
+ // TODO (b/254661666): Change to @EnabledSince(U) in next OS release
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID = 255659651L;
+
+ /**
+ * The FGS type enforcement: Starting a FGS from apps with targetSdkVersion
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later but without the required
+ * permissions associated with the FGS type will result in a SecurityException.
+ *
+ * @hide
+ */
+ // TODO (b/254661666): Change to @EnabledAfter(T)
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_NONE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_NONE,
+ FGS_TYPE_NONE_DEPRECATION_CHANGE_ID,
+ FGS_TYPE_NONE_DISABLED_CHANGE_ID,
+ null,
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_DATA_SYNC =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+ FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID,
+ FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC)
+ }, true),
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PLAYBACK =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK)
+ }, true),
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_PHONE_CALL}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_PHONE_CALL =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.MANAGE_OWN_CALLS)
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_LOCATION =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_LOCATION,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_LOCATION)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.ACCESS_COARSE_LOCATION),
+ new RegularPermission(Manifest.permission.ACCESS_FINE_LOCATION),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CONNECTED_DEVICE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.BLUETOOTH_CONNECT),
+ new RegularPermission(Manifest.permission.CHANGE_NETWORK_STATE),
+ new RegularPermission(Manifest.permission.CHANGE_WIFI_STATE),
+ new RegularPermission(Manifest.permission.CHANGE_WIFI_MULTICAST_STATE),
+ new RegularPermission(Manifest.permission.NFC),
+ new RegularPermission(Manifest.permission.TRANSMIT_IR),
+ new UsbDevicePermission(),
+ new UsbAccessoryPermission(),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PROJECTION =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT),
+ new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA)
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CAMERA =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_CAMERA,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CAMERA)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.CAMERA),
+ new RegularPermission(Manifest.permission.SYSTEM_CAMERA),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MICROPHONE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MICROPHONE,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD),
+ new RegularPermission(Manifest.permission.CAPTURE_AUDIO_OUTPUT),
+ new RegularPermission(Manifest.permission.CAPTURE_MEDIA_OUTPUT),
+ new RegularPermission(Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT),
+ new RegularPermission(Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT),
+ new RegularPermission(Manifest.permission.RECORD_AUDIO),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_HEALTH =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_HEALTH,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
+ new RegularPermission(Manifest.permission.BODY_SENSORS),
+ new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_REMOTE_MESSAGING =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING)
+ }, true),
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SYSTEM_EXEMPTED =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.SCHEDULE_EXACT_ALARM),
+ new RegularPermission(Manifest.permission.USE_EXACT_ALARM),
+ new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN),
+ new AppOpPermission(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SPECIAL_USE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE)
+ }, true),
+ null
+ );
+
+ /**
+ * Foreground service policy check result code: this one is not actually being used.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_UNKNOWN =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+
+ /**
+ * Foreground service policy check result code: okay to go.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_OK =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_OK;
+
+ /**
+ * Foreground service policy check result code: this foreground service type is deprecated.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_DEPRECATED =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+
+ /**
+ * Foreground service policy check result code: this foreground service type is disabled.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_DISABLED =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_DISABLED;
+
+ /**
+ * Foreground service policy check result code: the caller doesn't have permission to start
+ * foreground service with this type, but the policy is permissive.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+
+ /**
+ * Foreground service policy check result code: the caller doesn't have permission to start
+ * foreground service with this type, and the policy is enforced.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FGS_TYPE_POLICY_CHECK_" }, value = {
+ FGS_TYPE_POLICY_CHECK_UNKNOWN,
+ FGS_TYPE_POLICY_CHECK_OK,
+ FGS_TYPE_POLICY_CHECK_DEPRECATED,
+ FGS_TYPE_POLICY_CHECK_DISABLED,
+ FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE,
+ FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForegroundServicePolicyCheckCode{}
+
+ /**
+ * @return The policy info for the given type.
+ */
+ @NonNull
+ public abstract ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+ @ForegroundServiceType int type, @ForegroundServiceType int defaultToType);
+
+ /**
+ * Run check on the foreground service type policy for the given uid/pid
+ *
+ * @hide
+ */
+ @ForegroundServicePolicyCheckCode
+ public abstract int checkForegroundServiceTypePolicy(@NonNull Context context,
+ @NonNull String packageName, int callerUid, int callerPid, boolean allowWhileInUse,
+ @NonNull ForegroundServiceTypePolicyInfo policy);
+
+ @GuardedBy("sLock")
+ private static ForegroundServiceTypePolicy sDefaultForegroundServiceTypePolicy = null;
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Return the default policy for FGS type.
+ */
+ public static @NonNull ForegroundServiceTypePolicy getDefaultPolicy() {
+ synchronized (sLock) {
+ if (sDefaultForegroundServiceTypePolicy == null) {
+ sDefaultForegroundServiceTypePolicy = new DefaultForegroundServiceTypePolicy();
+ }
+ return sDefaultForegroundServiceTypePolicy;
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @hide
+ */
+ public ForegroundServiceTypePolicy() {
+ }
+
+ /**
+ * This class represents the policy for a specific FGS service type.
+ *
+ * @hide
+ */
+ public static final class ForegroundServiceTypePolicyInfo {
+ /**
+ * The foreground service type.
+ */
+ final @ForegroundServiceType int mType;
+
+ /**
+ * The change id to tell if this FGS type is deprecated.
+ *
+ * <p>A 0 indicates it's not deprecated.</p>
+ */
+ final long mDeprecationChangeId;
+
+ /**
+ * The change id to tell if this FGS type is disabled.
+ *
+ * <p>A 0 indicates it's not disabled.</p>
+ */
+ final long mDisabledChangeId;
+
+ /**
+ * The required permissions to start a foreground with this type, all of them
+ * MUST have been granted.
+ */
+ final @Nullable ForegroundServiceTypePermissions mAllOfPermissions;
+
+ /**
+ * The required permissions to start a foreground with this type, any one of them
+ * being granted is sufficient.
+ */
+ final @Nullable ForegroundServiceTypePermissions mAnyOfPermissions;
+
+ /**
+ * A customized check for the permissions.
+ */
+ @Nullable ForegroundServiceTypePermission mCustomPermission;
+
+ /**
+ * Not a real change id, but a place holder.
+ */
+ private static final long INVALID_CHANGE_ID = 0L;
+
+ /**
+ * @return {@code true} if the given change id is valid.
+ */
+ private static boolean isValidChangeId(long changeId) {
+ return changeId != INVALID_CHANGE_ID;
+ }
+
+ /**
+ * Construct a new instance.
+ *
+ * @hide
+ */
+ public ForegroundServiceTypePolicyInfo(@ForegroundServiceType int type,
+ long deprecationChangeId, long disabledChangeId,
+ @Nullable ForegroundServiceTypePermissions allOfPermissions,
+ @Nullable ForegroundServiceTypePermissions anyOfPermissions) {
+ mType = type;
+ mDeprecationChangeId = deprecationChangeId;
+ mDisabledChangeId = disabledChangeId;
+ mAllOfPermissions = allOfPermissions;
+ mAnyOfPermissions = anyOfPermissions;
+ }
+
+ /**
+ * @return The foreground service type.
+ */
+ @ForegroundServiceType
+ public int getForegroundServiceType() {
+ return mType;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = toPermissionString(new StringBuilder());
+ sb.append("type=0x");
+ sb.append(Integer.toHexString(mType));
+ sb.append(" deprecationChangeId=");
+ sb.append(mDeprecationChangeId);
+ sb.append(" disabledChangeId=");
+ sb.append(mDisabledChangeId);
+ sb.append(" customPermission=");
+ sb.append(mCustomPermission);
+ return sb.toString();
+ }
+
+ /**
+ * @return The required permissions.
+ */
+ public String toPermissionString() {
+ return toPermissionString(new StringBuilder()).toString();
+ }
+
+ private StringBuilder toPermissionString(StringBuilder sb) {
+ if (mAllOfPermissions != null) {
+ sb.append("all of the permissions ");
+ sb.append(mAllOfPermissions.toString());
+ sb.append(' ');
+ }
+ if (mAnyOfPermissions != null) {
+ sb.append("any of the permissions ");
+ sb.append(mAnyOfPermissions.toString());
+ sb.append(' ');
+ }
+ return sb;
+ }
+
+ /**
+ * @hide
+ */
+ public void setCustomPermission(
+ @Nullable ForegroundServiceTypePermission customPermission) {
+ mCustomPermission = customPermission;
+ }
+
+ /**
+ * @return The name of the permissions which are all required.
+ * It may contain app op names.
+ *
+ * For test only.
+ */
+ public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest() {
+ if (mAllOfPermissions == null) {
+ return Optional.empty();
+ }
+ return Optional.of(mAllOfPermissions.toStringArray());
+ }
+
+ /**
+ * @return The name of the permissions where any of the is granted is sufficient.
+ * It may contain app op names.
+ *
+ * For test only.
+ */
+ public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest() {
+ if (mAnyOfPermissions == null) {
+ return Optional.empty();
+ }
+ return Optional.of(mAnyOfPermissions.toStringArray());
+ }
+
+ /**
+ * Whether or not this type is disabled.
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public boolean isTypeDisabled(int callerUid) {
+ return isValidChangeId(mDisabledChangeId)
+ && CompatChanges.isChangeEnabled(mDisabledChangeId, callerUid);
+ }
+
+ /**
+ * Override the type disabling change Id.
+ *
+ * For test only.
+ */
+ public void setTypeDisabledForTest(boolean disabled, @NonNull String packageName)
+ throws RemoteException {
+ overrideChangeIdForTest(mDisabledChangeId, disabled, packageName);
+ }
+
+ /**
+ * clear the type disabling change Id.
+ *
+ * For test only.
+ */
+ public void clearTypeDisabledForTest(@NonNull String packageName) throws RemoteException {
+ clearOverrideForTest(mDisabledChangeId, packageName);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ boolean isTypeDeprecated(int callerUid) {
+ return isValidChangeId(mDeprecationChangeId)
+ && CompatChanges.isChangeEnabled(mDeprecationChangeId, callerUid);
+ }
+
+ private void overrideChangeIdForTest(long changeId, boolean enable, String packageName)
+ throws RemoteException {
+ if (!isValidChangeId(changeId)) {
+ return;
+ }
+ final ArraySet<Long> enabled = new ArraySet<>();
+ final ArraySet<Long> disabled = new ArraySet<>();
+ if (enable) {
+ enabled.add(changeId);
+ } else {
+ disabled.add(changeId);
+ }
+ final CompatibilityChangeConfig overrides = new CompatibilityChangeConfig(
+ new Compatibility.ChangeConfig(enabled, disabled));
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ platformCompat.setOverridesForTest(overrides, packageName);
+ }
+
+ private void clearOverrideForTest(long changeId, @NonNull String packageName)
+ throws RemoteException {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ platformCompat.clearOverrideForTest(changeId, packageName);
+ }
+ }
+
+ /**
+ * This represents the set of permissions that's going to be required
+ * for a specific service type.
+ *
+ * @hide
+ */
+ public static class ForegroundServiceTypePermissions {
+ /**
+ * The set of the permissions to be required.
+ */
+ final @NonNull ForegroundServiceTypePermission[] mPermissions;
+
+ /**
+ * Are we requiring all of the permissions to be granted or any of them.
+ */
+ final boolean mAllOf;
+
+ /**
+ * Constructor.
+ */
+ public ForegroundServiceTypePermissions(
+ @NonNull ForegroundServiceTypePermission[] permissions, boolean allOf) {
+ mPermissions = permissions;
+ mAllOf = allOf;
+ }
+
+ /**
+ * Check the permissions.
+ */
+ @PackageManager.PermissionResult
+ public int checkPermissions(@NonNull Context context, int callerUid, int callerPid,
+ @NonNull String packageName, boolean allowWhileInUse) {
+ if (mAllOf) {
+ for (ForegroundServiceTypePermission perm : mPermissions) {
+ final int result = perm.checkPermission(context, callerUid, callerPid,
+ packageName, allowWhileInUse);
+ if (result != PERMISSION_GRANTED) {
+ return PERMISSION_DENIED;
+ }
+ }
+ return PERMISSION_GRANTED;
+ } else {
+ boolean anyOfGranted = false;
+ for (ForegroundServiceTypePermission perm : mPermissions) {
+ final int result = perm.checkPermission(context, callerUid, callerPid,
+ packageName, allowWhileInUse);
+ if (result == PERMISSION_GRANTED) {
+ anyOfGranted = true;
+ break;
+ }
+ }
+ return anyOfGranted ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("allOf=");
+ sb.append(mAllOf);
+ sb.append(' ');
+ sb.append('[');
+ for (int i = 0; i < mPermissions.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(mPermissions[i].toString());
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ @NonNull String[] toStringArray() {
+ final String[] names = new String[mPermissions.length];
+ for (int i = 0; i < mPermissions.length; i++) {
+ names[i] = mPermissions[i].mName;
+ }
+ return names;
+ }
+ }
+
+ /**
+ * This represents a permission that's going to be required for a specific service type.
+ *
+ * @hide
+ */
+ public abstract static class ForegroundServiceTypePermission {
+ /**
+ * The name of this permission.
+ */
+ final @NonNull String mName;
+
+ /**
+ * Constructor.
+ */
+ public ForegroundServiceTypePermission(@NonNull String name) {
+ mName = name;
+ }
+
+ /**
+ * Check if the given uid/pid/package has the access to the permission.
+ */
+ @PackageManager.PermissionResult
+ public abstract int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+ @NonNull String packageName, boolean allowWhileInUse);
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
+
+ /**
+ * This represents a regular Android permission to be required for a specific service type.
+ */
+ static class RegularPermission extends ForegroundServiceTypePermission {
+ RegularPermission(@NonNull String name) {
+ super(name);
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ // Simple case, check if it's already granted.
+ if (context.checkPermission(mName, callerPid, callerUid) == PERMISSION_GRANTED) {
+ return PERMISSION_GRANTED;
+ }
+ if (allowWhileInUse) {
+ // Check its appops
+ final int opCode = AppOpsManager.permissionToOpCode(mName);
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ if (opCode != AppOpsManager.OP_NONE) {
+ final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
+ packageName);
+ if (currentMode == MODE_FOREGROUND) {
+ // It's in foreground only mode and we're allowing while-in-use.
+ return PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * This represents an app op permission to be required for a specific service type.
+ */
+ static class AppOpPermission extends ForegroundServiceTypePermission {
+ final int mOpCode;
+
+ AppOpPermission(int opCode) {
+ super(AppOpsManager.opToPublicName(opCode));
+ mOpCode = opCode;
+ }
+
+ @Override
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final int mode = appOpsManager.unsafeCheckOpRawNoThrow(mOpCode, callerUid, packageName);
+ return (mode == MODE_ALLOWED || (allowWhileInUse && mode == MODE_FOREGROUND))
+ ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * This represents a special Android permission to be required for accessing usb devices.
+ */
+ static class UsbDevicePermission extends ForegroundServiceTypePermission {
+ UsbDevicePermission() {
+ super("USB Device");
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final UsbManager usbManager = context.getSystemService(UsbManager.class);
+ final HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+ if (!ArrayUtils.isEmpty(devices)) {
+ for (UsbDevice device : devices.values()) {
+ if (usbManager.hasPermission(device, packageName, callerPid, callerUid)) {
+ return PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * This represents a special Android permission to be required for accessing usb accessories.
+ */
+ static class UsbAccessoryPermission extends ForegroundServiceTypePermission {
+ UsbAccessoryPermission() {
+ super("USB Accessory");
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final UsbManager usbManager = context.getSystemService(UsbManager.class);
+ final UsbAccessory[] accessories = usbManager.getAccessoryList();
+ if (!ArrayUtils.isEmpty(accessories)) {
+ for (UsbAccessory accessory: accessories) {
+ if (usbManager.hasPermission(accessory, callerPid, callerUid)) {
+ return PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * The default policy for the foreground service types.
+ *
+ * @hide
+ */
+ public static class DefaultForegroundServiceTypePolicy extends ForegroundServiceTypePolicy {
+ private final SparseArray<ForegroundServiceTypePolicyInfo> mForegroundServiceTypePolicies =
+ new SparseArray<>();
+
+ /**
+ * Constructor
+ */
+ public DefaultForegroundServiceTypePolicy() {
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_NONE,
+ FGS_TYPE_POLICY_NONE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+ FGS_TYPE_POLICY_DATA_SYNC);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ FGS_TYPE_POLICY_MEDIA_PLAYBACK);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+ FGS_TYPE_POLICY_PHONE_CALL);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_LOCATION,
+ FGS_TYPE_POLICY_LOCATION);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+ FGS_TYPE_POLICY_CONNECTED_DEVICE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+ FGS_TYPE_POLICY_MEDIA_PROJECTION);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CAMERA,
+ FGS_TYPE_POLICY_CAMERA);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MICROPHONE,
+ FGS_TYPE_POLICY_MICROPHONE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_HEALTH,
+ FGS_TYPE_POLICY_HEALTH);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+ FGS_TYPE_POLICY_REMOTE_MESSAGING);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+ FGS_TYPE_POLICY_SPECIAL_USE);
+ }
+
+ @Override
+ public ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+ @ForegroundServiceType int type, @ForegroundServiceType int defaultToType) {
+ ForegroundServiceTypePolicyInfo info = mForegroundServiceTypePolicies.get(type);
+ if (info == null) {
+ // Unknown type, fallback to the defaultToType
+ info = mForegroundServiceTypePolicies.get(defaultToType);
+ if (info == null) {
+ // It shouldn't happen.
+ throw new IllegalArgumentException("Invalid default fgs type " + defaultToType);
+ }
+ }
+ return info;
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @ForegroundServicePolicyCheckCode
+ public int checkForegroundServiceTypePolicy(Context context, String packageName,
+ int callerUid, int callerPid, boolean allowWhileInUse,
+ @NonNull ForegroundServiceTypePolicyInfo policy) {
+ // Has this FGS type been disabled and not allowed to use anymore?
+ if (policy.isTypeDisabled(callerUid)) {
+ return FGS_TYPE_POLICY_CHECK_DISABLED;
+ }
+ int permissionResult = PERMISSION_DENIED;
+ // Do we have the permission to start FGS with this type.
+ if (policy.mAllOfPermissions != null) {
+ permissionResult = policy.mAllOfPermissions.checkPermissions(context,
+ callerUid, callerPid, packageName, allowWhileInUse);
+ }
+ // If it has the "all of" permissions granted, check the "any of" ones.
+ if (permissionResult == PERMISSION_GRANTED) {
+ boolean checkCustomPermission = true;
+ // Check the "any of" permissions.
+ if (policy.mAnyOfPermissions != null) {
+ permissionResult = policy.mAnyOfPermissions.checkPermissions(context,
+ callerUid, callerPid, packageName, allowWhileInUse);
+ if (permissionResult == PERMISSION_GRANTED) {
+ // We have one of them granted, no need to check custom permissions.
+ checkCustomPermission = false;
+ }
+ }
+ // If we have a customized permission checker, also call it now.
+ if (checkCustomPermission && policy.mCustomPermission != null) {
+ permissionResult = policy.mCustomPermission.checkPermission(context,
+ callerUid, callerPid, packageName, allowWhileInUse);
+ }
+ }
+ if (permissionResult != PERMISSION_GRANTED) {
+ return (CompatChanges.isChangeEnabled(
+ FGS_TYPE_PERMISSION_CHANGE_ID, callerUid))
+ ? FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED
+ : FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+ }
+ // Has this FGS type been deprecated?
+ if (policy.isTypeDeprecated(callerUid)) {
+ return FGS_TYPE_POLICY_CHECK_DEPRECATED;
+ }
+ return FGS_TYPE_POLICY_CHECK_OK;
+ }
+ }
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9aa67bc..62481ba 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -145,10 +145,9 @@
void unregisterRemoteAnimations(in IBinder token);
/**
- * Reports that an Activity received a back key press when there were no additional activities
- * on the back stack.
+ * Reports that an Activity received a back key press.
*/
- oneway void onBackPressedOnTaskRoot(in IBinder activityToken,
+ oneway void onBackPressed(in IBinder activityToken,
in IRequestFinishCallback callback);
/** Reports that the splash screen view has attached to activity. */
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 37c5cab..8111184 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -16,9 +16,12 @@
package android.app;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.IBackupCallback;
import android.app.backup.IBackupManager;
import android.os.ParcelFileDescriptor;
+
+import com.android.internal.infra.AndroidFuture;
/**
* Interface presented by applications being asked to participate in the
@@ -193,4 +196,14 @@
* @param message The message to be passed to the agent's application in an exception.
*/
void fail(String message);
+
+ /**
+ * Provides the logging results that were accumulated in the BackupAgent during a backup or
+ * restore operation. This method should be called after the agent completes its backup or
+ * restore.
+ *
+ * @param resultsFuture a future that is completed with the logging results.
+ */
+ void getLoggerResults(
+ in AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture);
}
diff --git a/core/java/android/app/IRequestFinishCallback.aidl b/core/java/android/app/IRequestFinishCallback.aidl
index 22c20c8..72426df 100644
--- a/core/java/android/app/IRequestFinishCallback.aidl
+++ b/core/java/android/app/IRequestFinishCallback.aidl
@@ -18,7 +18,7 @@
/**
* This callback allows ActivityTaskManager to ask the calling Activity
- * to finish in response to a call to onBackPressedOnTaskRoot.
+ * to finish in response to a call to onBackPressed.
*
* {@hide}
*/
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 2e83308..99f864c 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -57,8 +57,7 @@
* @deprecated IntentService is subject to all the
* <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
* imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager}
- * or {@link androidx.core.app.JobIntentService}, which uses jobs
- * instead of services when running on Android 8.0 or higher.
+ * instead.
*/
@Deprecated
public abstract class IntentService extends Service {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index f3fc468..8ec313ec 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -11,6 +11,7 @@
per-file ApplicationThreadConstants.java = file:/services/core/java/com/android/server/am/OWNERS
per-file BroadcastOptions.java = file:/services/core/java/com/android/server/am/OWNERS
per-file ContentProviderHolder* = file:/services/core/java/com/android/server/am/OWNERS
+per-file ForegroundService* = file:/services/core/java/com/android/server/am/OWNERS
per-file IActivityController.aidl = file:/services/core/java/com/android/server/am/OWNERS
per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS
per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS
@@ -29,10 +30,12 @@
per-file Service* = file:/services/core/java/com/android/server/am/OWNERS
per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS
per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
-per-file UiAutomation* = file:/services/accessibility/OWNERS
+per-file *UiAutomation* = file:/services/accessibility/OWNERS
per-file GameManager* = file:/GAME_MANAGER_OWNERS
+per-file GameMode* = file:/GAME_MANAGER_OWNERS
per-file GameState* = file:/GAME_MANAGER_OWNERS
per-file IGameManager* = file:/GAME_MANAGER_OWNERS
+per-file IGameMode* = file:/GAME_MANAGER_OWNERS
# ActivityThread
per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index e220627..620adbe 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -72,21 +72,6 @@
/**
* Exception used to crash an app process when the system received a RemoteException
- * while delivering a broadcast to an app process.
- *
- * @hide
- */
- public static class CannotDeliverBroadcastException extends RemoteServiceException {
- /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 2;
-
- public CannotDeliverBroadcastException(String msg) {
- super(msg);
- }
- }
-
- /**
- * Exception used to crash an app process when the system received a RemoteException
* while posting a notification of a foreground service.
*
* @hide
@@ -94,7 +79,7 @@
public static class CannotPostForegroundServiceNotificationException
extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 3;
+ public static final int TYPE_ID = 2;
public CannotPostForegroundServiceNotificationException(String msg) {
super(msg);
@@ -109,7 +94,7 @@
*/
public static class BadForegroundServiceNotificationException extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 4;
+ public static final int TYPE_ID = 3;
public BadForegroundServiceNotificationException(String msg) {
super(msg);
@@ -125,7 +110,7 @@
public static class MissingRequestPasswordComplexityPermissionException
extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 5;
+ public static final int TYPE_ID = 4;
public MissingRequestPasswordComplexityPermissionException(String msg) {
super(msg);
@@ -139,7 +124,7 @@
*/
public static class CrashedByAdbException extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 6;
+ public static final int TYPE_ID = 5;
public CrashedByAdbException(String msg) {
super(msg);
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 5388282..bd5d105 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -396,6 +396,17 @@
private final String mSuggestActionMsg;
private final String mSuggestActionMsgColumn;
+ public static final Parcelable.Creator<ActionKeyInfo> CREATOR =
+ new Parcelable.Creator<ActionKeyInfo>() {
+ public ActionKeyInfo createFromParcel(Parcel in) {
+ return new ActionKeyInfo(in);
+ }
+
+ public ActionKeyInfo[] newArray(int size) {
+ return new ActionKeyInfo[size];
+ }
+ };
+
/**
* Create one object using attributeset as input data.
* @param activityContext runtime context of the activity that the action key information
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 7635138..754e3b6 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -17,10 +17,13 @@
package android.app;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.text.TextUtils.formatSimple;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -33,6 +36,7 @@
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.Log;
import android.view.contentcapture.ContentCaptureManager;
@@ -726,10 +730,32 @@
* for more details.
* </div>
*
+ * <div class="caution">
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * or higher are not allowed to start foreground services without specifying a valid
+ * foreground service type in the manifest attribute
+ * {@link android.R.attr#foregroundServiceType}.
+ * See
+ * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+ * Behavior changes: Apps targeting Android 14
+ * </a>
+ * for more details.
+ * </div>
+ *
* @throws ForegroundServiceStartNotAllowedException
* If the app targeting API is
* {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
* becoming foreground service due to background restriction.
+ * @throws ForegroundServiceTypeNotAllowedException
+ * If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+ * {@link android.R.attr#foregroundServiceType} is not set.
+ * @throws SecurityException If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+ * permission to start the foreground service with the specified type in the manifest attribute
+ * {@link android.R.attr#foregroundServiceType}.
*
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
@@ -740,64 +766,94 @@
*/
public final void startForeground(int id, Notification notification) {
try {
+ final ComponentName comp = new ComponentName(this, mClassName);
mActivityManager.setServiceForeground(
- new ComponentName(this, mClassName), mToken, id,
+ comp, mToken, id,
notification, 0, FOREGROUND_SERVICE_TYPE_MANIFEST);
clearStartForegroundServiceStackTrace();
+ logForegroundServiceStart(comp, FOREGROUND_SERVICE_TYPE_MANIFEST);
} catch (RemoteException ex) {
}
}
- /**
- * An overloaded version of {@link #startForeground(int, Notification)} with additional
- * foregroundServiceType parameter.
- *
- * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
- * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
- * service element of manifest file. The value of attribute
- * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
- *
- * <p>The foregroundServiceType parameter must be a subset flags of what is specified in manifest
- * attribute {@link android.R.attr#foregroundServiceType}, if not, an IllegalArgumentException is
- * thrown. Specify foregroundServiceType parameter as
- * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
- * is specified in manifest attribute foregroundServiceType.</p>
- *
- * <div class="caution">
- * <p><strong>Note:</strong>
- * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
- * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
- * or higher are not allowed to start foreground services from the background.
- * See
- * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
- * Behavior changes: Apps targeting Android 12
- * </a>
- * for more details.
- * </div>
- *
- * @param id The identifier for this notification as per
- * {@link NotificationManager#notify(int, Notification)
- * NotificationManager.notify(int, Notification)}; must not be 0.
- * @param notification The Notification to be displayed.
- * @param foregroundServiceType must be a subset flags of manifest attribute
- * {@link android.R.attr#foregroundServiceType} flags.
- *
- * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
- * attribute {@link android.R.attr#foregroundServiceType}.
- * @throws ForegroundServiceStartNotAllowedException
- * If the app targeting API is
- * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
- * becoming foreground service due to background restriction.
- *
- * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
- */
+ /**
+ * An overloaded version of {@link #startForeground(int, Notification)} with additional
+ * foregroundServiceType parameter.
+ *
+ * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
+ * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
+ * service element of manifest file. The value of attribute
+ * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
+ *
+ * <p>The foregroundServiceType parameter must be a subset flags of what is specified in
+ * manifest attribute {@link android.R.attr#foregroundServiceType}, if not, an
+ * IllegalArgumentException is thrown. Specify foregroundServiceType parameter as
+ * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
+ * is specified in manifest attribute foregroundServiceType.</p>
+ *
+ * <div class="caution">
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
+ * or higher are not allowed to start foreground services from the background.
+ * See
+ * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * Behavior changes: Apps targeting Android 12
+ * </a>
+ * for more details.
+ * </div>
+ *
+ * <div class="caution">
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * or higher are not allowed to start foreground services without specifying a valid
+ * foreground service type in the manifest attribute
+ * {@link android.R.attr#foregroundServiceType}, and the parameter {@code foregroundServiceType}
+ * here must not be the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ * See
+ * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+ * Behavior changes: Apps targeting Android 14
+ * </a>
+ * for more details.
+ * </div>
+ *
+ * @param id The identifier for this notification as per
+ * {@link NotificationManager#notify(int, Notification)
+ * NotificationManager.notify(int, Notification)}; must not be 0.
+ * @param notification The Notification to be displayed.
+ * @param foregroundServiceType must be a subset flags of manifest attribute
+ * {@link android.R.attr#foregroundServiceType} flags; must not be
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
+ * attribute {@link android.R.attr#foregroundServiceType}.
+ * @throws ForegroundServiceStartNotAllowedException
+ * If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
+ * becoming foreground service due to background restriction.
+ * @throws ForegroundServiceTypeNotAllowedException
+ * If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+ * {@link android.R.attr#foregroundServiceType} is not set, or the param
+ * {@code foregroundServiceType} is {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ * @throws SecurityException If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+ * permission to start the foreground service with the specified type in
+ * {@code foregroundServiceType}.
+ * {@link android.R.attr#foregroundServiceType}.
+ *
+ * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
+ */
public final void startForeground(int id, @NonNull Notification notification,
- @ForegroundServiceType int foregroundServiceType) {
+ @RequiresPermission @ForegroundServiceType int foregroundServiceType) {
try {
+ final ComponentName comp = new ComponentName(this, mClassName);
mActivityManager.setServiceForeground(
- new ComponentName(this, mClassName), mToken, id,
+ comp, mToken, id,
notification, 0, foregroundServiceType);
clearStartForegroundServiceStackTrace();
+ logForegroundServiceStart(comp, foregroundServiceType);
} catch (RemoteException ex) {
}
}
@@ -848,6 +904,7 @@
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, 0, null,
notificationBehavior, 0);
+ logForegroundServiceStopIfNecessary();
} catch (RemoteException ex) {
}
}
@@ -943,6 +1000,7 @@
*/
public final void detachAndCleanUp() {
mToken = null;
+ logForegroundServiceStopIfNecessary();
}
final String getClassName() {
@@ -976,6 +1034,50 @@
private boolean mStartCompatibility = false;
/**
+ * This will be set to the title of the system trace when this service is started as
+ * a foreground service, and will be set to null when it's no longer in foreground
+ * service state.
+ */
+ @GuardedBy("mForegroundServiceTraceTitleLock")
+ private @Nullable String mForegroundServiceTraceTitle = null;
+
+ private final Object mForegroundServiceTraceTitleLock = new Object();
+
+ private static final String TRACE_TRACK_NAME_FOREGROUND_SERVICE = "FGS";
+
+ private void logForegroundServiceStart(ComponentName comp,
+ @ForegroundServiceType int foregroundServiceType) {
+ synchronized (mForegroundServiceTraceTitleLock) {
+ if (mForegroundServiceTraceTitle == null) {
+ mForegroundServiceTraceTitle = formatSimple("comp=%s type=%s",
+ comp.toShortString(), Integer.toHexString(foregroundServiceType));
+ // The service is not in foreground state, emit a start event.
+ Trace.asyncTraceForTrackBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+ mForegroundServiceTraceTitle,
+ System.identityHashCode(this));
+ } else {
+ // The service is already in foreground state, emit an one-off event.
+ Trace.instantForTrack(TRACE_TAG_ACTIVITY_MANAGER,
+ TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+ mForegroundServiceTraceTitle);
+ }
+ }
+ }
+
+ private void logForegroundServiceStopIfNecessary() {
+ synchronized (mForegroundServiceTraceTitleLock) {
+ if (mForegroundServiceTraceTitle != null) {
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_ACTIVITY_MANAGER,
+ TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+ mForegroundServiceTraceTitle,
+ System.identityHashCode(this));
+ mForegroundServiceTraceTitle = null;
+ }
+ }
+ }
+
+ /**
* This keeps track of the stacktrace where Context.startForegroundService() was called
* for each service class. We use that when we crash the app for not calling
* {@link #startForeground} in time, in {@link ActivityThread#throwRemoteServiceException}.
@@ -1004,4 +1106,16 @@
return sStartForegroundServiceStackTraces.get(className);
}
}
+
+ /**
+ * Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ *
+ * TODO Implement it
+ * TODO Javadoc
+ *
+ * @param startId
+ * @hide
+ */
+ public void onTimeout(int startId) {
+ }
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 6d28972..a035375 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -229,6 +229,8 @@
public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
/** @hide */
public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
+ /** @hide */
+ public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
/**
* Session flag for {@link #registerSessionListener} indicating the listener
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 5e15b0a..6f1737a 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1318,23 +1318,21 @@
}
/**
- * Returns the information about the wallpaper if the current wallpaper is
- * a live wallpaper component. Otherwise, if the wallpaper is a static image,
- * this returns null.
+ * Returns the information about the home screen wallpaper if its current wallpaper is a live
+ * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
*/
public WallpaperInfo getWallpaperInfo() {
- return getWallpaperInfo(mContext.getUserId());
+ return getWallpaperInfoForUser(mContext.getUserId());
}
/**
- * Returns the information about the wallpaper if the current wallpaper is
- * a live wallpaper component. Otherwise, if the wallpaper is a static image,
- * this returns null.
+ * Returns the information about the home screen wallpaper if its current wallpaper is a live
+ * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
*
* @param userId Owner of the wallpaper.
* @hide
*/
- public WallpaperInfo getWallpaperInfo(int userId) {
+ public WallpaperInfo getWallpaperInfoForUser(int userId) {
try {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -1348,6 +1346,35 @@
}
/**
+ * Returns the information about the home screen wallpaper if its current wallpaper is a live
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+ * returns null.
+ *
+ * @param which Specifies wallpaper to request (home or lock).
+ * @throws IllegalArgumentException if {@code which} is not exactly one of
+ * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
+ * @hide
+ */
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
+ return getWallpaperInfo();
+ }
+
+ /**
+ * Returns the information about the designated wallpaper if its current wallpaper is a live
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+ * returns null.
+ *
+ * @param which Specifies wallpaper to request (home or lock).
+ * @param userId Owner of the wallpaper.
+ * @throws IllegalArgumentException if {@code which} is not exactly one of
+ * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
+ * @hide
+ */
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
+ return getWallpaperInfoForUser(userId);
+ }
+
+ /**
* Get the ID of the current wallpaper of the given kind. If there is no
* such wallpaper configured, returns a negative number.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f4cee5a..be4df9d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -103,6 +103,7 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -171,6 +172,7 @@
private final boolean mParentInstance;
private final DevicePolicyResourcesManager mResourcesManager;
+
/** @hide */
public DevicePolicyManager(Context context, IDevicePolicyManager service) {
this(context, service, false);
@@ -3822,6 +3824,27 @@
public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
/**
+ * Prevent an app from being placed into app standby buckets, such that it will not be subject
+ * to device resources restrictions as a result of app standby buckets.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EXEMPT_FROM_APP_STANDBY = 0;
+
+ /**
+ * Exemptions to platform restrictions, given to an application through
+ * {@link #setApplicationExemptions(String, Set)}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "EXEMPT_FROM_"}, value = {
+ EXEMPT_FROM_APP_STANDBY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApplicationExemptionConstants {}
+
+ /**
* Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management
* resources with IDs {@link #EXTRA_RESOURCE_IDS} has been updated, the updated resources can be
* retrieved using {@link DevicePolicyResourcesManager#getDrawable} and
@@ -6207,46 +6230,46 @@
public static final int WIPE_SILENTLY = 0x0008;
/**
- * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
- * other users will remain unaffected. Calling from the primary user will cause the device to
- * reboot, erasing all device data - including all the secondary users and their data - while
- * booting up.
- * <p>
- * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
- * be able to call this method; if it has not, a security exception will be thrown.
- *
- * If the caller is a profile owner of an organization-owned managed profile, it may
- * additionally call this method on the parent instance.
- * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
- * entire device, while calling it on the current profile instance would relinquish the device
- * for personal use, removing the managed profile and all policies set by the profile owner.
+ * See {@link #wipeData(int, CharSequence)}
*
* @param flags Bit mask of additional options: currently supported flags are
- * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
- * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
- * @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
- * {@link android.Manifest.permission#MASTER_CLEAR} permission.
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+ * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+ * @throws SecurityException if the calling application does not own an active
+ * administrator
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is
+ * not granted the
+ * {@link android.Manifest.permission#MASTER_CLEAR} permission.
+ * @throws IllegalStateException if called on last full-user or system-user
+ * @see #wipeDevice(int)
+ * @see #wipeData(int, CharSequence)
*/
public void wipeData(int flags) {
- wipeDataInternal(flags, "");
+ wipeDataInternal(flags,
+ /* wipeReasonForUser= */ "",
+ /* factoryReset= */ false);
}
/**
- * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
- * other users will remain unaffected, the provided reason for wiping data can be shown to
- * user. Calling from the primary user will cause the device to reboot, erasing all device data
- * - including all the secondary users and their data - while booting up. In this case, we don't
- * show the reason to the user since the device would be factory reset.
- * <p>
- * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
- * be able to call this method; if it has not, a security exception will be thrown.
+ * Ask that all user data be wiped.
*
- * If the caller is a profile owner of an organization-owned managed profile, it may
- * additionally call this method on the parent instance.
- * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
- * entire device, while calling it on the current profile instance would relinquish the device
- * for personal use, removing the managed profile and all policies set by the profile owner.
+ * <p>
+ * If called as a secondary user or managed profile, the user itself and its associated user
+ * data will be wiped. In particular, If the caller is a profile owner of an
+ * organization-owned managed profile, calling this method will relinquish the device for
+ * personal use, removing the managed profile and all policies set by the profile owner.
+ * </p>
+ *
+ * <p>
+ * Calling this method from the primary user will only work if the calling app is targeting
+ * Android 13 or below, in which case it will cause the device to reboot, erasing all device
+ * data - including all the secondary users and their data - while booting up. If an app
+ * targeting Android 13+ is calling this method from the primary user or last full user,
+ * {@link IllegalStateException} will be thrown.
+ * </p>
+ *
+ * If an app wants to wipe the entire device irrespective of which user they are from, they
+ * should use {@link #wipeDevice} instead.
*
* @param flags Bit mask of additional options: currently supported flags are
* {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
@@ -6254,30 +6277,61 @@
* @param reason a string that contains the reason for wiping data, which can be
* presented to the user.
* @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not granted the
* {@link android.Manifest.permission#MASTER_CLEAR} permission.
* @throws IllegalArgumentException if the input reason string is null or empty, or if
* {@link #WIPE_SILENTLY} is set.
+ * @throws IllegalStateException if called on last full-user or system-user
+ * @see #wipeDevice(int)
+ * @see #wipeData(int)
*/
public void wipeData(int flags, @NonNull CharSequence reason) {
Objects.requireNonNull(reason, "reason string is null");
Preconditions.checkStringNotEmpty(reason, "reason string is empty");
Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
- wipeDataInternal(flags, reason.toString());
+ wipeDataInternal(flags, reason.toString(), /* factoryReset= */ false);
}
/**
- * Internal function for both {@link #wipeData(int)} and
- * {@link #wipeData(int, CharSequence)} to call.
+ * Ask that the device be wiped and factory reset.
*
+ * <p>
+ * The calling Device Owner or Organization Owned Profile Owner must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call this method; if it has
+ * not, a security exception will be thrown.
+ *
+ * @param flags Bit mask of additional options: currently supported flags are
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+ * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+ * @throws SecurityException if the calling application does not own an active administrator
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not
+ * granted the {@link android.Manifest.permission#MASTER_CLEAR}
+ * permission.
* @see #wipeData(int)
* @see #wipeData(int, CharSequence)
- * @hide
*/
- private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
+ // TODO(b/255323293) Add host-side tests
+ public void wipeDevice(int flags) {
+ wipeDataInternal(flags,
+ /* wipeReasonForUser= */ "",
+ /* factoryReset= */ true);
+ }
+
+ /**
+ * Internal function for {@link #wipeData(int)}, {@link #wipeData(int, CharSequence)}
+ * and {@link #wipeDevice(int)} to call.
+ *
+ * @hide
+ * @see #wipeData(int)
+ * @see #wipeData(int, CharSequence)
+ * @see #wipeDevice(int)
+ */
+ private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser,
+ boolean factoryReset) {
if (mService != null) {
try {
- mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance);
+ mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance,
+ factoryReset);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8642,7 +8696,7 @@
public void reportFailedPasswordAttempt(int userHandle) {
if (mService != null) {
try {
- mService.reportFailedPasswordAttempt(userHandle);
+ mService.reportFailedPasswordAttempt(userHandle, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -14695,6 +14749,95 @@
}
/**
+ * Service-specific error code used in {@link #setApplicationExemptions(String, Set)} and
+ * {@link #getApplicationExemptions(String)}.
+ * @hide
+ */
+ public static final int ERROR_PACKAGE_NAME_NOT_FOUND = 1;
+
+ /**
+ * Called by an application with the
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission, to
+ * grant platform restriction exemptions to a given application.
+ *
+ * @param packageName The package name of the application to be exempt.
+ * @param exemptions The set of exemptions to be applied.
+ * @throws SecurityException If the caller does not have
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+ * @throws NameNotFoundException If either the package is not installed or the package is not
+ * visible to the caller.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+ public void setApplicationExemptions(@NonNull String packageName,
+ @NonNull @ApplicationExemptionConstants Set<Integer> exemptions)
+ throws NameNotFoundException {
+ throwIfParentInstance("setApplicationExemptions");
+ if (mService != null) {
+ try {
+ mService.setApplicationExemptions(packageName,
+ ArrayUtils.convertToIntArray(new ArraySet<>(exemptions)));
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ERROR_PACKAGE_NAME_NOT_FOUND:
+ throw new NameNotFoundException(e.getMessage());
+ default:
+ throw new RuntimeException(
+ "Unknown error setting application exemptions: " + e.errorCode, e);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns all the platform restriction exemptions currently applied to an application. Called
+ * by an application with the
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission.
+ *
+ * @param packageName The package name to check.
+ * @return A set of platform restrictions an application is exempt from.
+ * @throws SecurityException If the caller does not have
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+ * @throws NameNotFoundException If either the package is not installed or the package is not
+ * visible to the caller.
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+ public Set<Integer> getApplicationExemptions(@NonNull String packageName)
+ throws NameNotFoundException {
+ throwIfParentInstance("getApplicationExemptions");
+ if (mService == null) {
+ return Collections.emptySet();
+ }
+ try {
+ return intArrayToSet(mService.getApplicationExemptions(packageName));
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ERROR_PACKAGE_NAME_NOT_FOUND:
+ throw new NameNotFoundException(e.getMessage());
+ default:
+ throw new RuntimeException(
+ "Unknown error getting application exemptions: " + e.errorCode, e);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private Set<Integer> intArrayToSet(int[] array) {
+ Set<Integer> set = new ArraySet<>();
+ for (int item : array) {
+ set.add(item);
+ }
+ return set;
+ }
+
+ /**
* Called by a device owner or a profile owner to disable user control over apps. User will not
* be able to clear app data or force-stop packages. When called by a device owner, applies to
* all users on the device. Starting from Android 13, packages with user control disabled are
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 75bfc25..8a40265 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -117,7 +117,10 @@
void lockNow(int flags, boolean parent);
- void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
+ /**
+ * @param factoryReset only applicable when `targetSdk >= U`, either tries to factoryReset/fail or removeUser/fail otherwise
+ **/
+ void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent, boolean factoryReset);
void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
@@ -161,7 +164,7 @@
boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
void reportPasswordChanged(in PasswordMetrics metrics, int userId);
- void reportFailedPasswordAttempt(int userHandle);
+ void reportFailedPasswordAttempt(int userHandle, boolean parent);
void reportSuccessfulPasswordAttempt(int userHandle);
void reportFailedBiometricAttempt(int userHandle);
void reportSuccessfulBiometricAttempt(int userHandle);
@@ -565,4 +568,7 @@
boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
+
+ void setApplicationExemptions(String packageName, in int[]exemptions);
+ int[] getApplicationExemptions(String packageName);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index b1b59b0..a4f612d 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -41,6 +41,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import libcore.io.IoUtils;
@@ -202,6 +203,7 @@
Handler mHandler = null;
+ @Nullable private volatile BackupRestoreEventLogger mLogger = null;
@Nullable private UserHandle mUser;
// This field is written from the main thread (in onCreate), and read in a Binder thread (in
// onFullBackup that is called from system_server via Binder).
@@ -234,6 +236,20 @@
} catch (InterruptedException e) { /* ignored */ }
}
+ /**
+ * Get a logger to record app-specific backup and restore events that are happening during a
+ * backup or restore operation.
+ *
+ * <p>The logger instance had been created by the system with the correct {@link
+ * BackupRestoreEventLogger.OperationType} that corresponds to the operation the {@code
+ * BackupAgent} is currently handling.
+ *
+ * @hide
+ */
+ @Nullable
+ public BackupRestoreEventLogger getBackupRestoreEventLogger() {
+ return mLogger;
+ }
public BackupAgent() {
super(null);
@@ -264,6 +280,9 @@
* @hide
*/
public void onCreate(UserHandle user, @OperationType int operationType) {
+ // TODO: Instantiate with the correct type using a parameter.
+ mLogger = new BackupRestoreEventLogger(BackupRestoreEventLogger.OperationType.BACKUP);
+
onCreate();
mUser = user;
@@ -1305,6 +1324,16 @@
}
}
}
+
+ @Override
+ public void getLoggerResults(
+ AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in) {
+ if (mLogger != null) {
+ in.complete(mLogger.getLoggingResults());
+ } else {
+ in.complete(Collections.emptyList());
+ }
+ }
}
static class FailRunnable implements Runnable {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
similarity index 79%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
rename to core/java/android/app/backup/BackupRestoreEventLogger.aidl
index 817c209f..d6ef4e6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package android.app.backup;
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
+parcelable BackupRestoreEventLogger.DataTypeResult;
\ No newline at end of file
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 760c6f0..68740cb 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -19,6 +19,10 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
import android.util.Slog;
import java.lang.annotation.Retention;
@@ -53,6 +57,8 @@
/**
* Operation types for which this logger can be used.
+ *
+ * @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -87,6 +93,8 @@
* {@link OperationType}. Attempts to use logging methods that don't match
* the specified operation type will be rejected (e.g. use backup methods
* for a restore logger and vice versa).
+ *
+ * @hide
*/
public BackupRestoreEventLogger(@OperationType int operationType) {
mOperationType = operationType;
@@ -111,11 +119,9 @@
*
* @param dataType the type of data being backed.
* @param count number of items of the given type that have been successfully backed up.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
- return logSuccess(OperationType.BACKUP, dataType, count);
+ public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
+ logSuccess(OperationType.BACKUP, dataType, count);
}
/**
@@ -130,12 +136,10 @@
* @param dataType the type of data being backed.
* @param count number of items of the given type that have failed to back up.
* @param error optional, the error that has caused the failure.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+ public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return logFailure(OperationType.BACKUP, dataType, count, error);
+ logFailure(OperationType.BACKUP, dataType, count, error);
}
/**
@@ -151,12 +155,10 @@
*
* @param dataType the type of data being backed up.
* @param metaData the metadata associated with the data type.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+ public void logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metaData) {
- return logMetaData(OperationType.BACKUP, dataType, metaData);
+ logMetaData(OperationType.BACKUP, dataType, metaData);
}
/**
@@ -172,11 +174,9 @@
*
* @param dataType the type of data being restored.
* @param count number of items of the given type that have been successfully restored.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
- return logSuccess(OperationType.RESTORE, dataType, count);
+ public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
+ logSuccess(OperationType.RESTORE, dataType, count);
}
/**
@@ -193,12 +193,10 @@
* @param dataType the type of data being restored.
* @param count number of items of the given type that have failed to restore.
* @param error optional, the error that has caused the failure.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+ public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return logFailure(OperationType.RESTORE, dataType, count, error);
+ logFailure(OperationType.RESTORE, dataType, count, error);
}
/**
@@ -216,12 +214,10 @@
*
* @param dataType the type of data being restored.
* @param metadata the metadata associated with the data type.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
+ public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metadata) {
- return logMetaData(OperationType.RESTORE, dataType, metadata);
+ logMetaData(OperationType.RESTORE, dataType, metadata);
}
/**
@@ -240,52 +236,47 @@
*
* @hide
*/
- public @OperationType int getOperationType() {
+ @OperationType
+ public int getOperationType() {
return mOperationType;
}
- private boolean logSuccess(@OperationType int operationType,
+ private void logSuccess(@OperationType int operationType,
@BackupRestoreDataType String dataType, int count) {
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
- return false;
+ return;
}
dataTypeResult.mSuccessCount += count;
mResults.put(dataType, dataTypeResult);
-
- return true;
}
- private boolean logFailure(@OperationType int operationType,
+ private void logFailure(@OperationType int operationType,
@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
- return false;
+ return;
}
dataTypeResult.mFailCount += count;
if (error != null) {
dataTypeResult.mErrors.merge(error, count, Integer::sum);
}
-
- return true;
}
- private boolean logMetaData(@OperationType int operationType,
+ private void logMetaData(@OperationType int operationType,
@NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
if (mHashDigest == null) {
- return false;
+ return;
}
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
- return false;
+ return;
}
dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
-
- return true;
}
/**
@@ -325,7 +316,7 @@
/**
* Encapsulate logging results for a single data type.
*/
- public static class DataTypeResult {
+ public static class DataTypeResult implements Parcelable {
@BackupRestoreDataType
private final String mDataType;
private int mSuccessCount;
@@ -375,5 +366,57 @@
public byte[] getMetadataHash() {
return mMetadataHash;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mDataType);
+
+ dest.writeInt(mSuccessCount);
+
+ dest.writeInt(mFailCount);
+
+ Bundle errorsBundle = new Bundle();
+ for (Map.Entry<String, Integer> e : mErrors.entrySet()) {
+ errorsBundle.putInt(e.getKey(), e.getValue());
+ }
+ dest.writeBundle(errorsBundle);
+
+ dest.writeByteArray(mMetadataHash);
+ }
+
+ public static final Parcelable.Creator<DataTypeResult> CREATOR =
+ new Parcelable.Creator<>() {
+ public DataTypeResult createFromParcel(Parcel in) {
+ String dataType = in.readString();
+
+ int successCount = in.readInt();
+
+ int failCount = in.readInt();
+
+ Map<String, Integer> errors = new ArrayMap<>();
+ Bundle errorsBundle = in.readBundle(getClass().getClassLoader());
+ for (String key : errorsBundle.keySet()) {
+ errors.put(key, errorsBundle.getInt(key));
+ }
+
+ byte[] metadataHash = in.createByteArray();
+
+ DataTypeResult result = new DataTypeResult(dataType);
+ result.mSuccessCount = successCount;
+ result.mFailCount = failCount;
+ result.mErrors.putAll(errors);
+ result.mMetadataHash = metadataHash;
+ return result;
+ }
+
+ public DataTypeResult[] newArray(int size) {
+ return new DataTypeResult[size];
+ }
+ };
}
}
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 2581daa..d628b7f 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -30,6 +30,8 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import dalvik.system.CloseGuard;
import java.util.List;
@@ -79,6 +81,7 @@
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
private final AppPredictionSessionId mSessionId;
+ @GuardedBy("itself")
private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
/**
@@ -94,7 +97,7 @@
IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
mPredictionManager = IPredictionManager.Stub.asInterface(b);
mSessionId = new AppPredictionSessionId(
- context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+ context.getPackageName() + ":" + UUID.randomUUID(), context.getUserId());
try {
mPredictionManager.createPredictionSession(predictionContext, mSessionId, getToken());
} catch (RemoteException e) {
@@ -155,6 +158,15 @@
*/
public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ registerPredictionUpdatesLocked(callbackExecutor, callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void registerPredictionUpdatesLocked(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -183,6 +195,13 @@
* @param callback The callback to be unregistered.
*/
public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ unregisterPredictionUpdatesLocked(callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void unregisterPredictionUpdatesLocked(@NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -235,7 +254,7 @@
}
try {
- mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+ mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice<>(targets),
new CallbackWrapper(callbackExecutor, callback));
} catch (RemoteException e) {
Log.e(TAG, "Failed to sort targets", e);
@@ -251,19 +270,25 @@
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
- // Do destroy;
- try {
- mPredictionManager.onDestroyPredictionSession(mSessionId);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to notify app target event", e);
- e.rethrowAsRuntimeException();
+ synchronized (mRegisteredCallbacks) {
+ destroySessionLocked();
}
- mRegisteredCallbacks.clear();
} else {
throw new IllegalStateException("This client has already been destroyed.");
}
}
+ @GuardedBy("mRegisteredCallbacks")
+ private void destroySessionLocked() {
+ try {
+ mPredictionManager.onDestroyPredictionSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ e.rethrowAsRuntimeException();
+ }
+ mRegisteredCallbacks.clear();
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 2cd1d96..10db337 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -23,9 +23,11 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.util.Log;
import dalvik.system.CloseGuard;
@@ -70,7 +72,7 @@
* @hide
*/
@SystemApi
-public final class SearchSession implements AutoCloseable{
+public final class SearchSession implements AutoCloseable {
private static final String TAG = SearchSession.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -229,7 +231,14 @@
if (DEBUG) {
Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
}
- mExecutor.execute(() -> mCallback.accept(result.getList()));
+ List<SearchTarget> list = result.getList();
+ if (list.size() > 0) {
+ Bundle bundle = list.get(0).getExtras();
+ if (bundle != null) {
+ bundle.putLong("key_ipc_start", SystemClock.elapsedRealtime());
+ }
+ }
+ mExecutor.execute(() -> mCallback.accept(list));
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 30a6c31..ee14708 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -176,7 +176,6 @@
/** Write to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mClient.asBinder());
final boolean writeActivityToken = mActivityToken != null;
dest.writeBoolean(writeActivityToken);
if (writeActivityToken) {
@@ -192,7 +191,6 @@
/** Read from Parcel. */
private ClientTransaction(Parcel in) {
- mClient = (IApplicationThread) in.readStrongBinder();
final boolean readActivityToken = in.readBoolean();
if (readActivityToken) {
mActivityToken = in.readStrongBinder();
diff --git a/core/java/android/app/time/DetectorStatusTypes.java b/core/java/android/app/time/DetectorStatusTypes.java
new file mode 100644
index 0000000..3643fc9
--- /dev/null
+++ b/core/java/android/app/time/DetectorStatusTypes.java
@@ -0,0 +1,227 @@
+/*
+ * 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 android.app.time;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A set of constants that can relate to time or time zone detector status.
+ *
+ * <ul>
+ * <li>Detector status - the status of the overall detector.</li>
+ * <li>Detection algorithm status - the status of an algorithm that a detector can use.
+ * Each detector is expected to have one or more known algorithms to detect its chosen property,
+ * e.g. for time zone devices can have a "location" detection algorithm, where the device's
+ * location is used to detect the time zone.</li>
+ * </ul>
+ *
+ * @hide
+ */
+public final class DetectorStatusTypes {
+
+ /** A status code for a detector. */
+ @IntDef(prefix = "DETECTOR_STATUS_", value = {
+ DETECTOR_STATUS_UNKNOWN,
+ DETECTOR_STATUS_NOT_SUPPORTED,
+ DETECTOR_STATUS_NOT_RUNNING,
+ DETECTOR_STATUS_RUNNING,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DetectorStatus {}
+
+ /**
+ * The detector status is unknown. Expected only for use as a placeholder before the actual
+ * status is known.
+ */
+ public static final @DetectorStatus int DETECTOR_STATUS_UNKNOWN = 0;
+
+ /** The detector is not supported on this device. */
+ public static final @DetectorStatus int DETECTOR_STATUS_NOT_SUPPORTED = 1;
+
+ /** The detector is supported but is not running. */
+ public static final @DetectorStatus int DETECTOR_STATUS_NOT_RUNNING = 2;
+
+ /** The detector is supported and is running. */
+ public static final @DetectorStatus int DETECTOR_STATUS_RUNNING = 3;
+
+ private DetectorStatusTypes() {}
+
+ /**
+ * A status code for a detection algorithm.
+ */
+ @IntDef(prefix = "DETECTION_ALGORITHM_STATUS_", value = {
+ DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DetectionAlgorithmStatus {}
+
+ /**
+ * The detection algorithm status is unknown. Expected only for use as a placeholder before the
+ * actual status is known.
+ */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+
+ /** The detection algorithm is not supported on this device. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+
+ /** The detection algorithm supported but is not running. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+
+ /** The detection algorithm supported and is running. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+
+ /**
+ * Validates the supplied value is one of the known {@code DETECTOR_STATUS_} constants and
+ * returns it if it is valid. {@link #DETECTOR_STATUS_UNKNOWN} is considered valid.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ public static @DetectorStatus int requireValidDetectorStatus(
+ @DetectorStatus int detectorStatus) {
+ if (detectorStatus < DETECTOR_STATUS_UNKNOWN || detectorStatus > DETECTOR_STATUS_RUNNING) {
+ throw new IllegalArgumentException("Invalid detector status: " + detectorStatus);
+ }
+ return detectorStatus;
+ }
+
+ /**
+ * Returns a string for each {@code DETECTOR_STATUS_} constant. See also
+ * {@link #detectorStatusFromString(String)}.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ @NonNull
+ public static String detectorStatusToString(@DetectorStatus int detectorStatus) {
+ switch (detectorStatus) {
+ case DETECTOR_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case DETECTOR_STATUS_NOT_SUPPORTED:
+ return "NOT_SUPPORTED";
+ case DETECTOR_STATUS_NOT_RUNNING:
+ return "NOT_RUNNING";
+ case DETECTOR_STATUS_RUNNING:
+ return "RUNNING";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorStatus);
+ }
+ }
+
+ /**
+ * Returns {@code DETECTOR_STATUS_} constant value from a string. See also
+ * {@link #detectorStatusToString(int)}.
+ *
+ * @throws IllegalArgumentException if the value is not recognized or is invalid
+ */
+ public static @DetectorStatus int detectorStatusFromString(
+ @Nullable String detectorStatusString) {
+ if (TextUtils.isEmpty(detectorStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + detectorStatusString);
+ }
+
+ switch (detectorStatusString) {
+ case "UNKNOWN":
+ return DETECTOR_STATUS_UNKNOWN;
+ case "NOT_SUPPORTED":
+ return DETECTOR_STATUS_NOT_SUPPORTED;
+ case "NOT_RUNNING":
+ return DETECTOR_STATUS_NOT_RUNNING;
+ case "RUNNING":
+ return DETECTOR_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorStatusString);
+ }
+ }
+
+ /**
+ * Validates the supplied value is one of the known {@code DETECTION_ALGORITHM_} constants and
+ * returns it if it is valid. {@link #DETECTION_ALGORITHM_STATUS_UNKNOWN} is considered valid.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ public static @DetectionAlgorithmStatus int requireValidDetectionAlgorithmStatus(
+ @DetectionAlgorithmStatus int detectionAlgorithmStatus) {
+ if (detectionAlgorithmStatus < DETECTION_ALGORITHM_STATUS_UNKNOWN
+ || detectionAlgorithmStatus > DETECTION_ALGORITHM_STATUS_RUNNING) {
+ throw new IllegalArgumentException(
+ "Invalid detection algorithm: " + detectionAlgorithmStatus);
+ }
+ return detectionAlgorithmStatus;
+ }
+
+ /**
+ * Returns a string for each {@code DETECTION_ALGORITHM_} constant. See also
+ * {@link #detectionAlgorithmStatusFromString(String)}
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ @NonNull
+ public static String detectionAlgorithmStatusToString(
+ @DetectionAlgorithmStatus int detectorAlgorithmStatus) {
+ switch (detectorAlgorithmStatus) {
+ case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+ return "NOT_SUPPORTED";
+ case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+ return "NOT_RUNNING";
+ case DETECTION_ALGORITHM_STATUS_RUNNING:
+ return "RUNNING";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorAlgorithmStatus);
+ }
+ }
+
+ /**
+ * Returns {@code DETECTION_ALGORITHM_} constant value from a string. See also
+ * {@link #detectionAlgorithmStatusToString(int)} (String)}
+ *
+ * @throws IllegalArgumentException if the value is not recognized or is invalid
+ */
+ public static @DetectionAlgorithmStatus int detectionAlgorithmStatusFromString(
+ @Nullable String detectorAlgorithmStatusString) {
+
+ if (TextUtils.isEmpty(detectorAlgorithmStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + detectorAlgorithmStatusString);
+ }
+
+ switch (detectorAlgorithmStatusString) {
+ case "UNKNOWN":
+ return DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ case "NOT_SUPPORTED":
+ return DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ case "NOT_RUNNING":
+ return DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ case "RUNNING":
+ return DETECTION_ALGORITHM_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown status: " + detectorAlgorithmStatusString);
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
similarity index 79%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
index 817c209f..7184b12 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package android.app.time;
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
+parcelable LocationTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
new file mode 100644
index 0000000..710b8c4
--- /dev/null
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
@@ -0,0 +1,363 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.timezone.TimeZoneProviderStatus;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Information about the status of the location-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class LocationTimeZoneAlgorithmStatus implements Parcelable {
+
+ /**
+ * An enum that describes a location time zone provider's status.
+ *
+ * @hide
+ */
+ @IntDef(prefix = "PROVIDER_STATUS_", value = {
+ PROVIDER_STATUS_NOT_PRESENT,
+ PROVIDER_STATUS_NOT_READY,
+ PROVIDER_STATUS_IS_CERTAIN,
+ PROVIDER_STATUS_IS_UNCERTAIN,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProviderStatus {}
+
+ /**
+ * Indicates a provider is not present because it has not been configured, the configuration
+ * is bad, or the provider has reported a permanent failure.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_NOT_PRESENT = 1;
+
+ /**
+ * Indicates a provider has not reported it is certain or uncertain. This may be because it has
+ * just started running, or it has been stopped.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_NOT_READY = 2;
+
+ /**
+ * Indicates a provider last reported it is certain.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_IS_CERTAIN = 3;
+
+ /**
+ * Indicates a provider last reported it is uncertain.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_IS_UNCERTAIN = 4;
+
+ /**
+ * An instance that provides no information about algorithm status because the algorithm has not
+ * yet reported. Effectively a "null" status placeholder.
+ */
+ @NonNull
+ public static final LocationTimeZoneAlgorithmStatus UNKNOWN =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+
+ private final @DetectionAlgorithmStatus int mStatus;
+ private final @ProviderStatus int mPrimaryProviderStatus;
+ // May be populated when mPrimaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+ // or PROVIDER_STATUS_IS_UNCERTAIN
+ @Nullable private final TimeZoneProviderStatus mPrimaryProviderReportedStatus;
+
+ private final @ProviderStatus int mSecondaryProviderStatus;
+ // May be populated when mSecondaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+ // or PROVIDER_STATUS_IS_UNCERTAIN
+ @Nullable private final TimeZoneProviderStatus mSecondaryProviderReportedStatus;
+
+ public LocationTimeZoneAlgorithmStatus(
+ @DetectionAlgorithmStatus int status,
+ @ProviderStatus int primaryProviderStatus,
+ @Nullable TimeZoneProviderStatus primaryProviderReportedStatus,
+ @ProviderStatus int secondaryProviderStatus,
+ @Nullable TimeZoneProviderStatus secondaryProviderReportedStatus) {
+
+ mStatus = requireValidDetectionAlgorithmStatus(status);
+ mPrimaryProviderStatus = requireValidProviderStatus(primaryProviderStatus);
+ mPrimaryProviderReportedStatus = primaryProviderReportedStatus;
+ mSecondaryProviderStatus = requireValidProviderStatus(secondaryProviderStatus);
+ mSecondaryProviderReportedStatus = secondaryProviderReportedStatus;
+
+ boolean primaryProviderHasReported = hasProviderReported(primaryProviderStatus);
+ boolean primaryProviderReportedStatusPresent = primaryProviderReportedStatus != null;
+ if (!primaryProviderHasReported && primaryProviderReportedStatusPresent) {
+ throw new IllegalArgumentException(
+ "primaryProviderReportedStatus=" + primaryProviderReportedStatus
+ + ", primaryProviderStatus="
+ + providerStatusToString(primaryProviderStatus));
+ }
+
+ boolean secondaryProviderHasReported = hasProviderReported(secondaryProviderStatus);
+ boolean secondaryProviderReportedStatusPresent = secondaryProviderReportedStatus != null;
+ if (!secondaryProviderHasReported && secondaryProviderReportedStatusPresent) {
+ throw new IllegalArgumentException(
+ "secondaryProviderReportedStatus=" + secondaryProviderReportedStatus
+ + ", secondaryProviderStatus="
+ + providerStatusToString(secondaryProviderStatus));
+ }
+
+ // If the algorithm isn't running, providers can't report.
+ if (status != DETECTION_ALGORITHM_STATUS_RUNNING
+ && (primaryProviderHasReported || secondaryProviderHasReported)) {
+ throw new IllegalArgumentException(
+ "algorithmStatus=" + detectionAlgorithmStatusToString(status)
+ + ", primaryProviderReportedStatus=" + primaryProviderReportedStatus
+ + ", secondaryProviderReportedStatus="
+ + secondaryProviderReportedStatus);
+ }
+ }
+
+ /**
+ * Returns the status value of the detection algorithm.
+ */
+ public @DetectionAlgorithmStatus int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Returns the status of the primary location time zone provider as categorized by the detection
+ * algorithm.
+ */
+ public @ProviderStatus int getPrimaryProviderStatus() {
+ return mPrimaryProviderStatus;
+ }
+
+ /**
+ * Returns the status of the primary location time zone provider as reported by the provider
+ * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+ */
+ @Nullable
+ public TimeZoneProviderStatus getPrimaryProviderReportedStatus() {
+ return mPrimaryProviderReportedStatus;
+ }
+
+ /**
+ * Returns the status of the secondary location time zone provider as categorized by the
+ * detection algorithm.
+ */
+ public @ProviderStatus int getSecondaryProviderStatus() {
+ return mSecondaryProviderStatus;
+ }
+
+ /**
+ * Returns the status of the secondary location time zone provider as reported by the provider
+ * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+ */
+ @Nullable
+ public TimeZoneProviderStatus getSecondaryProviderReportedStatus() {
+ return mSecondaryProviderReportedStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "LocationTimeZoneAlgorithmStatus{"
+ + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mStatus)
+ + ", mPrimaryProviderStatus=" + providerStatusToString(mPrimaryProviderStatus)
+ + ", mPrimaryProviderReportedStatus=" + mPrimaryProviderReportedStatus
+ + ", mSecondaryProviderStatus=" + providerStatusToString(mSecondaryProviderStatus)
+ + ", mSecondaryProviderReportedStatus=" + mSecondaryProviderReportedStatus
+ + '}';
+ }
+
+ /**
+ * Parses a {@link LocationTimeZoneAlgorithmStatus} from a toString() string for manual
+ * command-line testing.
+ */
+ @NonNull
+ public static LocationTimeZoneAlgorithmStatus parseCommandlineArg(@NonNull String arg) {
+ // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based
+ // on OpenJDK code.
+ Pattern pattern = Pattern.compile("LocationTimeZoneAlgorithmStatus\\{"
+ + "mAlgorithmStatus=(.+)"
+ + ", mPrimaryProviderStatus=([^,]+)"
+ + ", mPrimaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+ + ", mSecondaryProviderStatus=([^,]+)"
+ + ", mSecondaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+ + "\\}"
+ );
+ Matcher matcher = pattern.matcher(arg);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Unable to parse algorithm status arg: " + arg);
+ }
+ @DetectionAlgorithmStatus int algorithmStatus =
+ detectionAlgorithmStatusFromString(matcher.group(1));
+ @ProviderStatus int primaryProviderStatus = providerStatusFromString(matcher.group(2));
+ TimeZoneProviderStatus primaryProviderReportedStatus =
+ parseTimeZoneProviderStatusOrNull(matcher.group(3));
+ @ProviderStatus int secondaryProviderStatus = providerStatusFromString(matcher.group(4));
+ TimeZoneProviderStatus secondaryProviderReportedStatus =
+ parseTimeZoneProviderStatusOrNull(matcher.group(5));
+ return new LocationTimeZoneAlgorithmStatus(
+ algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+ secondaryProviderStatus, secondaryProviderReportedStatus);
+ }
+
+ @Nullable
+ private static TimeZoneProviderStatus parseTimeZoneProviderStatusOrNull(
+ String providerReportedStatusString) {
+ TimeZoneProviderStatus providerReportedStatus;
+ if ("null".equals(providerReportedStatusString)) {
+ providerReportedStatus = null;
+ } else {
+ providerReportedStatus =
+ TimeZoneProviderStatus.parseProviderStatus(providerReportedStatusString);
+ }
+ return providerReportedStatus;
+ }
+
+ @NonNull
+ public static final Creator<LocationTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+ @Override
+ public LocationTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+ @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+ @ProviderStatus int primaryProviderStatus = in.readInt();
+ TimeZoneProviderStatus primaryProviderReportedStatus =
+ in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+ @ProviderStatus int secondaryProviderStatus = in.readInt();
+ TimeZoneProviderStatus secondaryProviderReportedStatus =
+ in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+ return new LocationTimeZoneAlgorithmStatus(
+ algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+ secondaryProviderStatus, secondaryProviderReportedStatus);
+ }
+
+ @Override
+ public LocationTimeZoneAlgorithmStatus[] newArray(int size) {
+ return new LocationTimeZoneAlgorithmStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mStatus);
+ parcel.writeInt(mPrimaryProviderStatus);
+ parcel.writeParcelable(mPrimaryProviderReportedStatus, flags);
+ parcel.writeInt(mSecondaryProviderStatus);
+ parcel.writeParcelable(mSecondaryProviderReportedStatus, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationTimeZoneAlgorithmStatus that = (LocationTimeZoneAlgorithmStatus) o;
+ return mStatus == that.mStatus
+ && mPrimaryProviderStatus == that.mPrimaryProviderStatus
+ && Objects.equals(
+ mPrimaryProviderReportedStatus, that.mPrimaryProviderReportedStatus)
+ && mSecondaryProviderStatus == that.mSecondaryProviderStatus
+ && Objects.equals(
+ mSecondaryProviderReportedStatus, that.mSecondaryProviderReportedStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus,
+ mPrimaryProviderStatus, mPrimaryProviderReportedStatus,
+ mSecondaryProviderStatus, mSecondaryProviderReportedStatus);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ @NonNull
+ public static String providerStatusToString(@ProviderStatus int providerStatus) {
+ switch (providerStatus) {
+ case PROVIDER_STATUS_NOT_PRESENT:
+ return "NOT_PRESENT";
+ case PROVIDER_STATUS_NOT_READY:
+ return "NOT_READY";
+ case PROVIDER_STATUS_IS_CERTAIN:
+ return "IS_CERTAIN";
+ case PROVIDER_STATUS_IS_UNCERTAIN:
+ return "IS_UNCERTAIN";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + providerStatus);
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting public static @ProviderStatus int providerStatusFromString(
+ @Nullable String providerStatusString) {
+ if (TextUtils.isEmpty(providerStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + providerStatusString);
+ }
+
+ switch (providerStatusString) {
+ case "NOT_PRESENT":
+ return PROVIDER_STATUS_NOT_PRESENT;
+ case "NOT_READY":
+ return PROVIDER_STATUS_NOT_READY;
+ case "IS_CERTAIN":
+ return PROVIDER_STATUS_IS_CERTAIN;
+ case "IS_UNCERTAIN":
+ return PROVIDER_STATUS_IS_UNCERTAIN;
+ default:
+ throw new IllegalArgumentException("Unknown status: " + providerStatusString);
+ }
+ }
+
+ private static boolean hasProviderReported(@ProviderStatus int providerStatus) {
+ return providerStatus == PROVIDER_STATUS_IS_CERTAIN
+ || providerStatus == PROVIDER_STATUS_IS_UNCERTAIN;
+ }
+
+ /** @hide */
+ @VisibleForTesting public static @ProviderStatus int requireValidProviderStatus(
+ @ProviderStatus int providerStatus) {
+ if (providerStatus < PROVIDER_STATUS_NOT_PRESENT
+ || providerStatus > PROVIDER_STATUS_IS_UNCERTAIN) {
+ throw new IllegalArgumentException(
+ "Invalid provider status: " + providerStatus);
+ }
+ return providerStatus;
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
similarity index 79%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
index 817c209f..0eb5b63 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package android.app.time;
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
+parcelable TelephonyTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
new file mode 100644
index 0000000..95240c0
--- /dev/null
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.NonNull;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the telephony-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class TelephonyTimeZoneAlgorithmStatus implements Parcelable {
+
+ private final @DetectionAlgorithmStatus int mAlgorithmStatus;
+
+ public TelephonyTimeZoneAlgorithmStatus(@DetectionAlgorithmStatus int algorithmStatus) {
+ mAlgorithmStatus = requireValidDetectionAlgorithmStatus(algorithmStatus);
+ }
+
+ /**
+ * Returns the status of the detection algorithm.
+ */
+ public @DetectionAlgorithmStatus int getAlgorithmStatus() {
+ return mAlgorithmStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TelephonyTimeZoneAlgorithmStatus{"
+ + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mAlgorithmStatus)
+ + '}';
+ }
+
+ @NonNull
+ public static final Creator<TelephonyTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+ @Override
+ public TelephonyTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+ @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+ return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+ }
+
+ @Override
+ public TelephonyTimeZoneAlgorithmStatus[] newArray(int size) {
+ return new TelephonyTimeZoneAlgorithmStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mAlgorithmStatus);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TelephonyTimeZoneAlgorithmStatus that = (TelephonyTimeZoneAlgorithmStatus) o;
+ return mAlgorithmStatus == that.mAlgorithmStatus;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAlgorithmStatus);
+ }
+}
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index cd91b04..4684c6a 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -23,27 +23,40 @@
import android.os.Parcelable;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
- * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
+ * An object containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
*
* @hide
*/
@SystemApi
public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
- public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR =
- new Creator<TimeZoneCapabilitiesAndConfig>() {
- public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
- return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
- }
+ public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR = new Creator<>() {
+ public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+ return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
+ }
- public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
- return new TimeZoneCapabilitiesAndConfig[size];
- }
- };
+ public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
+ return new TimeZoneCapabilitiesAndConfig[size];
+ }
+ };
-
+ /**
+ * The time zone detector status.
+ *
+ * Implementation note for future platform engineers: This field is only needed by SettingsUI
+ * initially and so it has not been added to the SDK API. {@link TimeZoneDetectorStatus}
+ * contains details about the internals of the time zone detector so thought should be given to
+ * abstraction / exposing a lightweight version if something unbundled needs access to detector
+ * details. Also, that could be good time to add separate APIs for bundled components, or add
+ * new APIs that return something more extensible and generic like a Bundle or a less
+ * constraining name. See also {@link
+ * TimeManager#addTimeZoneDetectorListener(Executor, TimeManager.TimeZoneDetectorListener)},
+ * which notified of changes to any fields in this class, including the detector status.
+ */
+ @NonNull private final TimeZoneDetectorStatus mDetectorStatus;
@NonNull private final TimeZoneCapabilities mCapabilities;
@NonNull private final TimeZoneConfiguration mConfiguration;
@@ -53,26 +66,41 @@
* @hide
*/
public TimeZoneCapabilitiesAndConfig(
+ @NonNull TimeZoneDetectorStatus detectorStatus,
@NonNull TimeZoneCapabilities capabilities,
@NonNull TimeZoneConfiguration configuration) {
- this.mCapabilities = Objects.requireNonNull(capabilities);
- this.mConfiguration = Objects.requireNonNull(configuration);
+ mDetectorStatus = Objects.requireNonNull(detectorStatus);
+ mCapabilities = Objects.requireNonNull(capabilities);
+ mConfiguration = Objects.requireNonNull(configuration);
}
@NonNull
private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
- TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class);
- TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class);
- return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
+ TimeZoneDetectorStatus detectorStatus =
+ in.readParcelable(null, TimeZoneDetectorStatus.class);
+ TimeZoneCapabilities capabilities = in.readParcelable(null, TimeZoneCapabilities.class);
+ TimeZoneConfiguration configuration = in.readParcelable(null, TimeZoneConfiguration.class);
+ return new TimeZoneCapabilitiesAndConfig(detectorStatus, capabilities, configuration);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mDetectorStatus, flags);
dest.writeParcelable(mCapabilities, flags);
dest.writeParcelable(mConfiguration, flags);
}
/**
+ * Returns the time zone detector's status.
+ *
+ * @hide
+ */
+ @NonNull
+ public TimeZoneDetectorStatus getDetectorStatus() {
+ return mDetectorStatus;
+ }
+
+ /**
* Returns the user's time zone behavior capabilities.
*/
@NonNull
@@ -102,7 +130,8 @@
return false;
}
TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o;
- return mCapabilities.equals(that.mCapabilities)
+ return mDetectorStatus.equals(that.mDetectorStatus)
+ && mCapabilities.equals(that.mCapabilities)
&& mConfiguration.equals(that.mConfiguration);
}
@@ -114,7 +143,8 @@
@Override
public String toString() {
return "TimeZoneCapabilitiesAndConfig{"
- + "mCapabilities=" + mCapabilities
+ + "mDetectorStatus=" + mDetectorStatus
+ + ", mCapabilities=" + mCapabilities
+ ", mConfiguration=" + mConfiguration
+ '}';
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
similarity index 79%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to core/java/android/app/time/TimeZoneDetectorStatus.aidl
index 817c209f..32204df 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package android.app.time;
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
+parcelable TimeZoneDetectorStatus;
diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.java b/core/java/android/app/time/TimeZoneDetectorStatus.java
new file mode 100644
index 0000000..1637463
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.java
@@ -0,0 +1,124 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DetectorStatus;
+import static android.app.time.DetectorStatusTypes.requireValidDetectorStatus;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the automatic time zone detector. Used by SettingsUI to display
+ * status information to the user.
+ *
+ * @hide
+ */
+public final class TimeZoneDetectorStatus implements Parcelable {
+
+ private final @DetectorStatus int mDetectorStatus;
+ @NonNull private final TelephonyTimeZoneAlgorithmStatus mTelephonyTimeZoneAlgorithmStatus;
+ @NonNull private final LocationTimeZoneAlgorithmStatus mLocationTimeZoneAlgorithmStatus;
+
+ public TimeZoneDetectorStatus(
+ @DetectorStatus int detectorStatus,
+ @NonNull TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus,
+ @NonNull LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus) {
+ mDetectorStatus = requireValidDetectorStatus(detectorStatus);
+ mTelephonyTimeZoneAlgorithmStatus =
+ Objects.requireNonNull(telephonyTimeZoneAlgorithmStatus);
+ mLocationTimeZoneAlgorithmStatus = Objects.requireNonNull(locationTimeZoneAlgorithmStatus);
+ }
+
+ public @DetectorStatus int getDetectorStatus() {
+ return mDetectorStatus;
+ }
+
+ @NonNull
+ public TelephonyTimeZoneAlgorithmStatus getTelephonyTimeZoneAlgorithmStatus() {
+ return mTelephonyTimeZoneAlgorithmStatus;
+ }
+
+ @NonNull
+ public LocationTimeZoneAlgorithmStatus getLocationTimeZoneAlgorithmStatus() {
+ return mLocationTimeZoneAlgorithmStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneDetectorStatus{"
+ + "mDetectorStatus=" + DetectorStatusTypes.detectorStatusToString(mDetectorStatus)
+ + ", mTelephonyTimeZoneAlgorithmStatus=" + mTelephonyTimeZoneAlgorithmStatus
+ + ", mLocationTimeZoneAlgorithmStatus=" + mLocationTimeZoneAlgorithmStatus
+ + '}';
+ }
+
+ public static final @NonNull Creator<TimeZoneDetectorStatus> CREATOR = new Creator<>() {
+ @Override
+ public TimeZoneDetectorStatus createFromParcel(Parcel in) {
+ @DetectorStatus int detectorStatus = in.readInt();
+ TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus =
+ in.readParcelable(getClass().getClassLoader(),
+ TelephonyTimeZoneAlgorithmStatus.class);
+ LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus =
+ in.readParcelable(getClass().getClassLoader(),
+ LocationTimeZoneAlgorithmStatus.class);
+ return new TimeZoneDetectorStatus(detectorStatus,
+ telephonyTimeZoneAlgorithmStatus, locationTimeZoneAlgorithmStatus);
+ }
+
+ @Override
+ public TimeZoneDetectorStatus[] newArray(int size) {
+ return new TimeZoneDetectorStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mDetectorStatus);
+ parcel.writeParcelable(mTelephonyTimeZoneAlgorithmStatus, flags);
+ parcel.writeParcelable(mLocationTimeZoneAlgorithmStatus, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneDetectorStatus that = (TimeZoneDetectorStatus) o;
+ return mDetectorStatus == that.mDetectorStatus
+ && mTelephonyTimeZoneAlgorithmStatus.equals(that.mTelephonyTimeZoneAlgorithmStatus)
+ && mLocationTimeZoneAlgorithmStatus.equals(that.mLocationTimeZoneAlgorithmStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDetectorStatus, mTelephonyTimeZoneAlgorithmStatus,
+ mLocationTimeZoneAlgorithmStatus);
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 0e9e28b..f357fb2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -81,11 +81,11 @@
String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
/**
- * A shell command that injects a geolocation time zone suggestion (as if from the
+ * A shell command that injects a location algorithm event (as if from the
* location_time_zone_manager).
* @hide
*/
- String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
+ String SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT = "handle_location_algorithm_event";
/**
* A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 90973c3..0d890a4 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -56,6 +57,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -84,50 +87,77 @@
private static final boolean DEBUG = false;
private static final String LOG_TAG = "CDM_CompanionDeviceManager";
+ /** @hide */
+ @IntDef(prefix = {"RESULT_"}, value = {
+ RESULT_OK,
+ RESULT_CANCELED,
+ RESULT_USER_REJECTED,
+ RESULT_DISCOVERY_TIMEOUT,
+ RESULT_INTERNAL_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
/**
- * The result code to propagate back to the originating activity, indicates the association
- * dialog is explicitly declined by the users.
- *
- * @hide
+ * The result code to propagate back to the user activity, indicates the association
+ * is created successfully.
+ */
+ public static final int RESULT_OK = -1;
+
+ /**
+ * The result code to propagate back to the user activity, indicates if the association dialog
+ * is implicitly cancelled.
+ * E.g. phone is locked, switch to another app or press outside the dialog.
+ */
+ public static final int RESULT_CANCELED = 0;
+
+ /**
+ * The result code to propagate back to the user activity, indicates the association dialog
+ * is explicitly declined by the users.
*/
public static final int RESULT_USER_REJECTED = 1;
/**
- * The result code to propagate back to the originating activity, indicates the association
+ * The result code to propagate back to the user activity, indicates the association
* dialog is dismissed if there's no device found after 20 seconds.
- *
- * @hide
*/
public static final int RESULT_DISCOVERY_TIMEOUT = 2;
/**
- * The result code to propagate back to the originating activity, indicates the internal error
+ * The result code to propagate back to the user activity, indicates the internal error
* in CompanionDeviceManager.
- *
- * @hide
*/
public static final int RESULT_INTERNAL_ERROR = 3;
/**
- * Requesting applications will receive the String in {@link Callback#onFailure} if the
- * association dialog is explicitly declined by the users. e.g. press the Don't allow button.
+ * Requesting applications will receive the String in {@link Callback#onFailure} if the
+ * association dialog is explicitly declined by the users. E.g. press the Don't allow
+ * button.
*
* @hide
*/
public static final String REASON_USER_REJECTED = "user_rejected";
/**
- * Requesting applications will receive the String in {@link Callback#onFailure} if there's
- * no device found after 20 seconds.
+ * Requesting applications will receive the String in {@link Callback#onFailure} if there's
+ * no devices found after 20 seconds.
*
* @hide
*/
public static final String REASON_DISCOVERY_TIMEOUT = "discovery_timeout";
/**
- * Requesting applications will receive the String in {@link Callback#onFailure} if the
- * association dialog is in-explicitly declined by the users. e.g. phone is locked, switch to
- * another app or press outside the dialog.
+ * Requesting applications will receive the String in {@link Callback#onFailure} if there's
+ * an internal error.
+ *
+ * @hide
+ */
+ public static final String REASON_INTERNAL_ERROR = "internal_error";
+
+ /**
+ * Requesting applications will receive the String in {@link Callback#onFailure} if the
+ * association dialog is implicitly cancelled. E.g. phone is locked, switch to
+ * another app or press outside the dialog.
*
* @hide
*/
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 82d7534..7d6336a 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -52,6 +52,11 @@
List<VirtualDevice> getVirtualDevices();
/**
+ * Returns the device policy for the given virtual device and policy type.
+ */
+ int getDevicePolicy(int deviceId, int policyType);
+
+ /**
* Creates a virtual display owned by a particular virtual device.
*
* @param virtualDisplayConfig The configuration used in creating the display
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 0bb86fb..c14bb1b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -182,6 +182,28 @@
}
/**
+ * Returns the device policy for the given virtual device and policy type.
+ *
+ * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+ * policy for that device and policy type, then
+ * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+ *
+ * @hide
+ */
+ public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+ int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service.");
+ return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+ }
+ try {
+ return mService.getDevicePolicy(deviceId, policyType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A virtual device has its own virtual display, audio output, microphone, and camera etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d40c9d6..f8c2e34a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -28,6 +28,7 @@
import android.os.Parcelable;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.SparseIntArray;
import com.android.internal.util.Preconditions;
@@ -103,6 +104,47 @@
*/
public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
+ /** @hide */
+ @IntDef(prefix = "DEVICE_POLICY_", value = {DEVICE_POLICY_DEFAULT, DEVICE_POLICY_CUSTOM})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface DevicePolicy {}
+
+ /**
+ * Indicates that there is no special logic for this virtual device and it should be treated
+ * the same way as the default device, keeping the default behavior unchanged.
+ */
+ public static final int DEVICE_POLICY_DEFAULT = 0;
+
+ /**
+ * Indicates that there is custom logic, specific to this virtual device, which should be
+ * triggered instead of the default behavior.
+ */
+ public static final int DEVICE_POLICY_CUSTOM = 1;
+
+ /**
+ * Any relevant component must be able to interpret the correct meaning of a custom policy for
+ * a given policy type.
+ * @hide
+ */
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface PolicyType {}
+
+ /**
+ * Tells the sensor framework how to handle sensor requests from contexts associated with this
+ * virtual device, namely the sensors returned by
+ * {@link android.hardware.SensorManager#getSensorList}:
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Return the sensors of the default device.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Return the sensors of the virtual device. Note that if
+ * the virtual device did not create any virtual sensors, then an empty list is returned.
+ * </ul>
+ */
+ public static final int POLICY_TYPE_SENSORS = 0;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -114,6 +156,8 @@
@ActivityPolicy
private final int mDefaultActivityPolicy;
@Nullable private final String mName;
+ // Mapping of @PolicyType to @DevicePolicy
+ @NonNull private final SparseIntArray mDevicePolicies;
private VirtualDeviceParams(
@LockState int lockState,
@@ -124,12 +168,14 @@
@NonNull Set<ComponentName> allowedActivities,
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
- @Nullable String name) {
+ @Nullable String name,
+ @NonNull SparseIntArray devicePolicies) {
Preconditions.checkNotNull(usersWithMatchingAccounts);
Preconditions.checkNotNull(allowedCrossTaskNavigations);
Preconditions.checkNotNull(blockedCrossTaskNavigations);
Preconditions.checkNotNull(allowedActivities);
Preconditions.checkNotNull(blockedActivities);
+ Preconditions.checkNotNull(devicePolicies);
mLockState = lockState;
mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
@@ -140,6 +186,7 @@
mBlockedActivities = new ArraySet<>(blockedActivities);
mDefaultActivityPolicy = defaultActivityPolicy;
mName = name;
+ mDevicePolicies = devicePolicies;
}
@SuppressWarnings("unchecked")
@@ -153,6 +200,7 @@
mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null);
mDefaultActivityPolicy = parcel.readInt();
mName = parcel.readString8();
+ mDevicePolicies = parcel.readSparseIntArray();
}
/**
@@ -258,6 +306,16 @@
return mName;
}
+ /**
+ * Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
+ * policy for this type has been explicitly specified.
+ *
+ * @see Builder#addDevicePolicy
+ */
+ public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
+ return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -274,6 +332,7 @@
dest.writeArraySet(mBlockedActivities);
dest.writeInt(mDefaultActivityPolicy);
dest.writeString8(mName);
+ dest.writeSparseIntArray(mDevicePolicies);
}
@Override
@@ -285,6 +344,18 @@
return false;
}
VirtualDeviceParams that = (VirtualDeviceParams) o;
+ final int devicePoliciesCount = mDevicePolicies.size();
+ if (devicePoliciesCount != that.mDevicePolicies.size()) {
+ return false;
+ }
+ for (int i = 0; i < devicePoliciesCount; i++) {
+ if (mDevicePolicies.keyAt(i) != that.mDevicePolicies.keyAt(i)) {
+ return false;
+ }
+ if (mDevicePolicies.valueAt(i) != that.mDevicePolicies.valueAt(i)) {
+ return false;
+ }
+ }
return mLockState == that.mLockState
&& mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
&& Objects.equals(mAllowedCrossTaskNavigations, that.mAllowedCrossTaskNavigations)
@@ -298,10 +369,15 @@
@Override
public int hashCode() {
- return Objects.hash(
+ int hashCode = Objects.hash(
mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
- mBlockedActivities, mDefaultActivityPolicy, mName);
+ mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
+ for (int i = 0; i < mDevicePolicies.size(); i++) {
+ hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
+ hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
+ }
+ return hashCode;
}
@Override
@@ -317,6 +393,7 @@
+ " mBlockedActivities=" + mBlockedActivities
+ " mDefaultActivityPolicy=" + mDefaultActivityPolicy
+ " mName=" + mName
+ + " mDevicePolicies=" + mDevicePolicies
+ ")";
}
@@ -350,6 +427,7 @@
private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
private boolean mDefaultActivityPolicyConfigured = false;
@Nullable private String mName;
+ @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -528,6 +606,21 @@
}
/**
+ * Add a policy for this virtual device.
+ *
+ * Policies define the system behavior that may be specific for this virtual device. A
+ * policy can be defined for each {@code PolicyType}, but they are all optional.
+ *
+ * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+ * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+ */
+ @NonNull
+ public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
+ mDevicePolicies.put(policyType, devicePolicy);
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
*/
@NonNull
@@ -541,7 +634,8 @@
mAllowedActivities,
mBlockedActivities,
mDefaultActivityPolicy,
- mName);
+ mName,
+ mDevicePolicies);
}
}
}
diff --git a/core/java/android/content/ActivityNotFoundException.java b/core/java/android/content/ActivityNotFoundException.java
index 16149bb..5b50189 100644
--- a/core/java/android/content/ActivityNotFoundException.java
+++ b/core/java/android/content/ActivityNotFoundException.java
@@ -31,5 +31,4 @@
{
super(name);
}
-};
-
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1df0fa8..ae1f689 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4917,6 +4917,7 @@
* @see android.server.attention.AttentionManagerService
* @hide
*/
+ @TestApi
public static final String ATTENTION_SERVICE = "attention";
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f2ebec6..9d82274 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3561,6 +3561,17 @@
public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
/**
+ * Broadcast action: Launch System output switcher. Includes a single extra field,
+ * {@link #EXTRA_PACKAGE_NAME}, which specifies the package name of the calling app
+ * so that the system can get the corresponding MediaSession for the output switcher.
+ *
+ * @see #EXTRA_PACKAGE_NAME
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SHOW_OUTPUT_SWITCHER =
+ "android.intent.action.SHOW_OUTPUT_SWITCHER";
+
+ /**
* Broadcast Action: The "Camera Button" was pressed. Includes a single
* extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
* caused the broadcast.
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index b3435b1..8b6c4dd 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -28,6 +28,7 @@
import android.os.PatternMatcher;
import android.text.TextUtils;
import android.util.AndroidException;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
@@ -302,7 +303,7 @@
@UnsupportedAppUsage
private int mOrder;
@UnsupportedAppUsage
- private final ArrayList<String> mActions;
+ private final ArraySet<String> mActions;
private ArrayList<String> mCategories = null;
private ArrayList<String> mDataSchemes = null;
private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
@@ -433,7 +434,7 @@
*/
public IntentFilter() {
mPriority = 0;
- mActions = new ArrayList<String>();
+ mActions = new ArraySet<>();
}
/**
@@ -445,7 +446,7 @@
*/
public IntentFilter(String action) {
mPriority = 0;
- mActions = new ArrayList<String>();
+ mActions = new ArraySet<>();
addAction(action);
}
@@ -468,7 +469,7 @@
public IntentFilter(String action, String dataType)
throws MalformedMimeTypeException {
mPriority = 0;
- mActions = new ArrayList<String>();
+ mActions = new ArraySet<>();
addAction(action);
addDataType(dataType);
}
@@ -481,7 +482,7 @@
public IntentFilter(IntentFilter o) {
mPriority = o.mPriority;
mOrder = o.mOrder;
- mActions = new ArrayList<String>(o.mActions);
+ mActions = new ArraySet<>(o.mActions);
if (o.mCategories != null) {
mCategories = new ArrayList<String>(o.mCategories);
}
@@ -742,9 +743,7 @@
* @param action Name of the action to match, such as Intent.ACTION_VIEW.
*/
public final void addAction(String action) {
- if (!mActions.contains(action)) {
- mActions.add(action.intern());
- }
+ mActions.add(action.intern());
}
/**
@@ -758,7 +757,7 @@
* Return an action in the filter.
*/
public final String getAction(int index) {
- return mActions.get(index);
+ return mActions.valueAt(index);
}
/**
@@ -797,8 +796,11 @@
if (ignoreActions == null) {
return !mActions.isEmpty();
}
+ if (mActions.size() > ignoreActions.size()) {
+ return true; // some actions are definitely not ignored
+ }
for (int i = mActions.size() - 1; i >= 0; i--) {
- if (!ignoreActions.contains(mActions.get(i))) {
+ if (!ignoreActions.contains(mActions.valueAt(i))) {
return true;
}
}
@@ -1918,7 +1920,7 @@
int N = countActions();
for (int i=0; i<N; i++) {
serializer.startTag(null, ACTION_STR);
- serializer.attribute(null, NAME_STR, mActions.get(i));
+ serializer.attribute(null, NAME_STR, mActions.valueAt(i));
serializer.endTag(null, ACTION_STR);
}
N = countCategories();
@@ -2313,7 +2315,7 @@
}
public final void writeToParcel(Parcel dest, int flags) {
- dest.writeStringList(mActions);
+ dest.writeStringArray(mActions.toArray(new String[mActions.size()]));
if (mCategories != null) {
dest.writeInt(1);
dest.writeStringList(mCategories);
@@ -2407,8 +2409,9 @@
/** @hide */
public IntentFilter(Parcel source) {
- mActions = new ArrayList<String>();
- source.readStringList(mActions);
+ List<String> actions = new ArrayList<>();
+ source.readStringList(actions);
+ mActions = new ArraySet<>(actions);
if (source.readInt() != 0) {
mCategories = new ArrayList<String>();
source.readStringList(mCategories);
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index f888813..1b5f64c 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -261,8 +261,8 @@
}
LongAtomicFormula that = (LongAtomicFormula) o;
return getKey() == that.getKey()
- && mValue == that.mValue
- && mOperator == that.mOperator;
+ && Objects.equals(mValue, that.mValue)
+ && Objects.equals(mOperator, that.mOperator);
}
@Override
@@ -628,7 +628,7 @@
return false;
}
BooleanAtomicFormula that = (BooleanAtomicFormula) o;
- return getKey() == that.getKey() && mValue == that.mValue;
+ return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
}
@Override
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 3ca0560..cc7977a 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -20,11 +20,13 @@
import android.annotation.Nullable;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
+import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.Objects;
/**
* Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
@@ -88,6 +90,27 @@
}
/**
+ * Ensure the resource name is in the form [package]:type/entry.
+ *
+ * @param name name of the target resource to overlay (in the form [package]:type/entry)
+ * @return the valid name
+ */
+ private static String ensureValidResourceName(@NonNull String name) {
+ Objects.requireNonNull(name);
+ final int slashIndex = name.indexOf('/'); /* must contain '/' */
+ final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */
+
+ // The minimum length of resource type is "id".
+ Preconditions.checkArgument(
+ slashIndex >= 0 /* It must contain the type name */
+ && colonIndex != 0 /* 0 means the package name is empty */
+ && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */,
+ "\"%s\" is invalid resource name",
+ name);
+ return name;
+ }
+
+ /**
* Sets the value of the fabricated overlay
*
* @param resourceName name of the target resource to overlay (in the form
@@ -98,6 +121,8 @@
* @see android.util.TypedValue#type
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -119,6 +144,8 @@
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
String configuration) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -139,6 +166,8 @@
* @see android.util.TypedValue#type
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -160,6 +189,8 @@
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
String configuration) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -169,6 +200,26 @@
return this;
}
+ /**
+ * Sets the value of the fabricated overlay
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param value the file descriptor whose contents are the value of the frro
+ * @param configuration The string representation of the config this overlay is enabled for
+ */
+ public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
+ String configuration) {
+ ensureValidResourceName(resourceName);
+
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.binaryData = value;
+ entry.configuration = configuration;
+ mEntries.add(entry);
+ return this;
+ }
+
/** Builds an immutable fabricated overlay. */
public FabricatedOverlay build() {
final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index b95f8bb..0a7d079 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -1,13 +1,11 @@
# Bug component: 36137
-patb@google.com
+file:/PACKAGE_MANAGER_OWNERS
-per-file Package* = file:/PACKAGE_MANAGER_OWNERS
per-file PackageParser.java = set noparent
per-file PackageParser.java = chiuwinson@google.com,patb@google.com
per-file *Capability* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
-per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
per-file UserInfo* = file:/MULTIUSER_OWNERS
per-file *UserProperties* = file:/MULTIUSER_OWNERS
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aa86af9..485d04d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -165,6 +165,21 @@
"android.internal.PROPERTY_NO_APP_DATA_STORAGE";
/**
+ * <service> level {@link android.content.pm.PackageManager.Property} tag specifying
+ * the actual use case of the service if it's foreground service with the type
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+ *
+ * <p>
+ * For example:
+ * <service>
+ * <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+ * android:value="foo"/>
+ * </service>
+ */
+ public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE =
+ "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
+
+ /**
* A property value set within the manifest.
* <p>
* The value of a property will only have a single type, as defined by
@@ -189,12 +204,12 @@
@VisibleForTesting
public Property(@NonNull String name, int type,
@NonNull String packageName, @Nullable String className) {
- assert name != null;
- assert type >= TYPE_BOOLEAN && type <= TYPE_STRING;
- assert packageName != null;
- this.mName = name;
+ if (type < TYPE_BOOLEAN || type > TYPE_STRING) {
+ throw new IllegalArgumentException("Invalid type");
+ }
+ this.mName = Objects.requireNonNull(name);
this.mType = type;
- this.mPackageName = packageName;
+ this.mPackageName = Objects.requireNonNull(packageName);
this.mClassName = className;
}
/** @hide */
@@ -442,9 +457,8 @@
*/
public ComponentEnabledSetting(@NonNull ComponentName componentName,
@EnabledState int newState, @EnabledFlags int flags) {
- Objects.nonNull(componentName);
mPackageName = null;
- mComponentName = componentName;
+ mComponentName = Objects.requireNonNull(componentName);
mEnabledState = newState;
mEnabledFlags = flags;
}
@@ -460,8 +474,7 @@
*/
public ComponentEnabledSetting(@NonNull String packageName,
@EnabledState int newState, @EnabledFlags int flags) {
- Objects.nonNull(packageName);
- mPackageName = packageName;
+ mPackageName = Objects.requireNonNull(packageName);
mComponentName = null;
mEnabledState = newState;
mEnabledFlags = flags;
@@ -2946,6 +2959,18 @@
public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
/**
+ * Feature for {@link #getSystemAvailableFeatures()} and {@link #hasSystemFeature(String)}.
+ * This feature indicates whether device supports
+ * <a href="https://source.android.com/docs/core/virtualization">Android Virtualization Framework</a>.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VIRTUALIZATION_FRAMEWORK =
+ "android.software.virtualization_framework";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
* implementation on this device is hardware accelerated, and the Vulkan native API will
@@ -3407,7 +3432,6 @@
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device is capable of communicating with
* other devices via ultra wideband.
- * @hide
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_UWB = "android.hardware.uwb";
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 7c22c088..61fc6f6 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -22,7 +22,6 @@
import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -184,7 +183,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000;
/**
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 88d7004..14f03ea 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -16,7 +16,9 @@
package android.content.pm;
+import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Printer;
@@ -100,7 +102,15 @@
/**
* The default foreground service type if not been set in manifest file.
+ *
+ * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+ * later should NOT use this type,
+ * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+ * this type will get a {@link android.app.ForegroundServiceTypeNotAllowedException}.</p>
+ *
+ * @deprecated Do not use.
*/
+ @Deprecated
public static final int FOREGROUND_SERVICE_TYPE_NONE = 0;
/**
@@ -108,14 +118,36 @@
* the {@link android.R.attr#foregroundServiceType} attribute.
* Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
* transfer over network between device and cloud.
+ *
+ * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+ * later should NOT use this type:
+ * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+ * this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} is still
+ * allowed, but calling it with this type on devices running future platform releases may get a
+ * {@link android.app.ForegroundServiceTypeNotAllowedException}.</p>
+ *
+ * @deprecated Use {@link android.app.job.JobInfo.Builder} data transfer APIs instead.
*/
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
+ conditional = true
+ )
+ @Deprecated
public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1 << 0;
/**
* Constant corresponding to <code>mediaPlayback</code> in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Music, video, news or other media playback.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
*/
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK,
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 1 << 1;
/**
@@ -123,28 +155,94 @@
* the {@link android.R.attr#foregroundServiceType} attribute.
* Ongoing operations related to phone calls, video conferencing,
* or similar interactive communication.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL,
+ },
+ anyOf = {
+ Manifest.permission.MANAGE_OWN_CALLS,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 1 << 2;
/**
* Constant corresponding to <code>location</code> in
* the {@link android.R.attr#foregroundServiceType} attribute.
* GPS, map, navigation location update.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+ * following permissions:
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_LOCATION,
+ },
+ anyOf = {
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 1 << 3;
/**
* Constant corresponding to <code>connectedDevice</code> in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+ * following permissions:
+ * {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+ * {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+ * {@link android.Manifest.permission#NFC},
+ * {@link android.Manifest.permission#TRANSMIT_IR},
+ * or has been granted the access to one of the attached USB devices/accessories.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE,
+ },
+ anyOf = {
+ Manifest.permission.BLUETOOTH_CONNECT,
+ Manifest.permission.CHANGE_NETWORK_STATE,
+ Manifest.permission.CHANGE_WIFI_STATE,
+ Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+ Manifest.permission.NFC,
+ Manifest.permission.TRANSMIT_IR,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 1 << 4;
/**
* Constant corresponding to {@code mediaProjection} in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Managing a media projection session, e.g for screen recording or taking screenshots.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user must
+ * have allowed the screen capture request from this app.
*/
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION,
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 1 << 5;
/**
@@ -155,7 +253,21 @@
* above, a foreground service will not be able to access the camera if this type is not
* specified in the manifest and in
* {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+ * {@link android.Manifest.permission#CAMERA}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_CAMERA,
+ },
+ anyOf = {
+ Manifest.permission.CAMERA,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 1 << 6;
/**
@@ -166,17 +278,170 @@
* above, a foreground service will not be able to access the microphone if this type is not
* specified in the manifest and in
* {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the following
+ * permissions:
+ * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+ * {@link android.Manifest.permission#RECORD_AUDIO}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_MICROPHONE,
+ },
+ anyOf = {
+ Manifest.permission.CAPTURE_AUDIO_OUTPUT,
+ Manifest.permission.RECORD_AUDIO,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
/**
- * The number of foreground service types, this doesn't include
- * the {@link #FOREGROUND_SERVICE_TYPE_MANIFEST} and {@link #FOREGROUND_SERVICE_TYPE_NONE}
- * as they're not real service types.
+ * Constant corresponding to {@code health} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Health, wellness and fitness.
+ *
+ * <p>The caller app is required to have the permissions
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+ * permissions:
+ * {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+ * {@link android.Manifest.permission#BODY_SENSORS},
+ * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+ */
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_HEALTH,
+ },
+ anyOf = {
+ Manifest.permission.ACTIVITY_RECOGNITION,
+ Manifest.permission.BODY_SENSORS,
+ Manifest.permission.HIGH_SAMPLING_RATE_SENSORS,
+ },
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 1 << 8;
+
+ /**
+ * Constant corresponding to {@code remoteMessaging} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Messaging use cases which host local server to relay messages across devices.
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING,
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 1 << 9;
+
+ /**
+ * Constant corresponding to {@code systemExempted} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * The system exmpted foreground service use cases.
+ *
+ * <p class="note">Note, apps are allowed to use this type only in the following cases:
+ * <ul>
+ * <li>App has a UID < {@link android.os.Process#FIRST_APPLICATION_UID}</li>
+ * <li>App is on Doze allowlist</li>
+ * <li>Device is running in <a href="https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/demo_mode.md">Demo Mode</a></li>
+ * <li><a href="https://source.android.com/devices/tech/admin/provision">Device owner app</a><li>
+ * <li><a href="https://source.android.com/devices/tech/admin/managed-profiles">Profile owner apps</a><li>
+ * <li>Persistent apps</li>
+ * <li><a href="https://source.android.com/docs/core/connect/carrier">Carrier privileged apps</a></li>
+ * <li>Apps that have the {@code android.app.role.RoleManager#ROLE_EMERGENCY} role</li>
+ * <li>Headless system apps</li>
+ * <li><a href="{@docRoot}guide/topics/admin/device-admin">Device admin apps</a></li>
+ * <li>Active VPN apps</li>
+ * <li>Apps holding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
+ * {@link Manifest.permission#USE_EXACT_ALARM} permission.</li>
+ * </ul>
+ * </p>
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED,
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
+
+ /**
+ * Foreground service type corresponding to {@code shortService} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ *
+ * TODO Implement it
+ *
+ * TODO Expand the javadoc
+ *
+ * This type is not associated with specific use cases unlike other types, but this has
+ * unique restrictions.
+ * <ul>
+ * <li>Has a timeout
+ * <li>Cannot start other foreground services from this
+ * <li>
+ * </ul>
+ *
+ * @see Service#onTimeout
*
* @hide
*/
- public static final int NUM_OF_FOREGROUND_SERVICE_TYPES = 8;
+ public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
+
+ /**
+ * Constant corresponding to {@code specialUse} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Use cases that can't be categorized into any other foreground service types, but also
+ * can't use {@link android.app.job.JobInfo.Builder} APIs.
+ *
+ * <p>The use of this foreground service type may be restricted. Additionally, apps must declare
+ * a service-level {@link PackageManager#PROPERTY_SPECIAL_USE_FGS_SUBTYPE <property>} in
+ * {@code AndroidManifest.xml} as a hint of what the exact use case here is.
+ * Here is an example:
+ * <pre>
+ * <uses-permission
+ * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+ * />
+ * <service
+ * android:name=".MySpecialForegroundService"
+ * android:foregroundServiceType="specialUse">
+ * <property
+ * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+ * android:value="foo"
+ * />
+ * </service>
+ * </pre>
+ *
+ * In a future release of Android, if the above foreground service type {@code foo} is supported
+ * by the platform, to offer the backward compatibility, the app could specify
+ * the {@code android:maxSdkVersion} attribute in the <uses-permission> section,
+ * and also add the foreground service type {@code foo} into
+ * the {@code android:foregroundServiceType}, therefore the same app could be installed
+ * in both platforms.
+ * <pre>
+ * <uses-permission
+ * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+ * android:maxSdkVersion="last_sdk_version_without_type_foo"
+ * />
+ * <service
+ * android:name=".MySpecialForegroundService"
+ * android:foregroundServiceType="specialUse|foo">
+ * <property
+ * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE""
+ * android:value="foo"
+ * />
+ * </service>
+ * </pre>
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE,
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1 << 30;
+
+ /**
+ * The max index being used in the definition of foreground service types.
+ *
+ * @hide
+ */
+ public static final int FOREGROUND_SERVICE_TYPES_MAX_INDEX = 30;
/**
* A special value indicates to use all types set in manifest file.
@@ -199,7 +464,12 @@
FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
FOREGROUND_SERVICE_TYPE_CAMERA,
- FOREGROUND_SERVICE_TYPE_MICROPHONE
+ FOREGROUND_SERVICE_TYPE_MICROPHONE,
+ FOREGROUND_SERVICE_TYPE_HEALTH,
+ FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+ FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ForegroundServiceType {}
@@ -275,6 +545,16 @@
return "camera";
case FOREGROUND_SERVICE_TYPE_MICROPHONE:
return "microphone";
+ case FOREGROUND_SERVICE_TYPE_HEALTH:
+ return "health";
+ case FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING:
+ return "remoteMessaging";
+ case FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED:
+ return "systemExempted";
+ case FOREGROUND_SERVICE_TYPE_SHORT_SERVICE:
+ return "shortService";
+ case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
+ return "specialUse";
default:
return "unknown";
}
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 1f83d75..295df5c 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,6 +50,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import java.lang.IllegalArgumentException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -1360,7 +1361,9 @@
@NonNull
public Builder setIntents(@NonNull Intent[] intents) {
Objects.requireNonNull(intents, "intents cannot be null");
- Objects.requireNonNull(intents.length, "intents cannot be empty");
+ if (intents.length == 0) {
+ throw new IllegalArgumentException("intents cannot be empty");
+ }
for (Intent intent : intents) {
Objects.requireNonNull(intent, "intents cannot contain null");
Objects.requireNonNull(intent.getAction(), "intent's action must be set");
@@ -1398,7 +1401,9 @@
@NonNull
public Builder setPersons(@NonNull Person[] persons) {
Objects.requireNonNull(persons, "persons cannot be null");
- Objects.requireNonNull(persons.length, "persons cannot be empty");
+ if (persons.length == 0) {
+ throw new IllegalArgumentException("persons cannot be empty");
+ }
for (Person person : persons) {
Objects.requireNonNull(person, "persons cannot contain null");
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 9baa6ba..2be0323 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -159,6 +159,18 @@
public static final int FLAG_EPHEMERAL_ON_CREATE = 0x00002000;
/**
+ * Indicates that this user is the designated main user on the device. This user may have access
+ * to certain features which are limited to at most one user.
+ *
+ * <p>Currently, this will be the first user to go through setup on the device, but in future
+ * releases this status may be transferable or may even not be given to any users.
+ *
+ * <p>This is not necessarily the system user. For example, it will not be the system user on
+ * devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true.
+ */
+ public static final int FLAG_MAIN = 0x00004000;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -175,7 +187,8 @@
FLAG_FULL,
FLAG_SYSTEM,
FLAG_PROFILE,
- FLAG_EPHEMERAL_ON_CREATE
+ FLAG_EPHEMERAL_ON_CREATE,
+ FLAG_MAIN
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserInfoFlag {
@@ -369,6 +382,13 @@
}
/**
+ * @see #FLAG_MAIN
+ */
+ public boolean isMain() {
+ return (flags & FLAG_MAIN) == FLAG_MAIN;
+ }
+
+ /**
* Returns true if the user is a split system user.
* <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
* the method always returns false.
diff --git a/core/java/android/content/pm/UserPackage.java b/core/java/android/content/pm/UserPackage.java
index e75f551..7ca92c3 100644
--- a/core/java/android/content/pm/UserPackage.java
+++ b/core/java/android/content/pm/UserPackage.java
@@ -33,17 +33,19 @@
* @hide
*/
public final class UserPackage {
+ private static final boolean ENABLE_CACHING = true;
+
@UserIdInt
public final int userId;
public final String packageName;
- @GuardedBy("sCache")
+ private static final Object sCacheLock = new Object();
+ @GuardedBy("sCacheLock")
private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>();
- private static final Object sUserIdLock = new Object();
private static final class NoPreloadHolder {
/** Set of userIDs to cache objects for. */
- @GuardedBy("sUserIdLock")
+ @GuardedBy("sCacheLock")
private static int[] sUserIds = new int[]{UserHandle.getUserId(Process.myUid())};
}
@@ -80,13 +82,16 @@
/** Return an instance of this class representing the given userId + packageName combination. */
@NonNull
public static UserPackage of(@UserIdInt int userId, @NonNull String packageName) {
- synchronized (sUserIdLock) {
+ if (!ENABLE_CACHING) {
+ return new UserPackage(userId, packageName);
+ }
+
+ synchronized (sCacheLock) {
if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) {
// Don't cache objects for invalid userIds.
return new UserPackage(userId, packageName);
}
- }
- synchronized (sCache) {
+
UserPackage up = sCache.get(userId, packageName);
if (up == null) {
packageName = packageName.intern();
@@ -99,23 +104,27 @@
/** Remove the specified app from the cache. */
public static void removeFromCache(@UserIdInt int userId, @NonNull String packageName) {
- synchronized (sCache) {
+ if (!ENABLE_CACHING) {
+ return;
+ }
+
+ synchronized (sCacheLock) {
sCache.delete(userId, packageName);
}
}
/** Indicate the list of valid user IDs on the device. */
public static void setValidUserIds(@NonNull int[] userIds) {
- userIds = userIds.clone();
- synchronized (sUserIdLock) {
- NoPreloadHolder.sUserIds = userIds;
+ if (!ENABLE_CACHING) {
+ return;
}
- synchronized (sCache) {
+
+ userIds = userIds.clone();
+ synchronized (sCacheLock) {
+ NoPreloadHolder.sUserIds = userIds;
+
for (int u = sCache.numMaps() - 1; u >= 0; --u) {
final int userId = sCache.keyAt(u);
- // Not holding sUserIdLock is intentional here. We don't modify the elements within
- // the array and so even if this method is called multiple times with different sets
- // of user IDs, we want to adjust the cache based on each new array.
if (!ArrayUtils.contains(userIds, userId)) {
sCache.deleteAt(u);
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 80f3264..fd35378 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -44,12 +44,16 @@
private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
private static final String ATTR_START_WITH_PARENT = "startWithParent";
private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
+ private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
+ private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
/** Index values of each property (to indicate whether they are present in this object). */
@IntDef(prefix = "INDEX_", value = {
INDEX_SHOW_IN_LAUNCHER,
INDEX_START_WITH_PARENT,
INDEX_SHOW_IN_SETTINGS,
+ INDEX_INHERIT_DEVICE_POLICY,
+ INDEX_USE_PARENTS_CONTACTS,
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -57,6 +61,8 @@
private static final int INDEX_SHOW_IN_LAUNCHER = 0;
private static final int INDEX_START_WITH_PARENT = 1;
private static final int INDEX_SHOW_IN_SETTINGS = 2;
+ private static final int INDEX_INHERIT_DEVICE_POLICY = 3;
+ private static final int INDEX_USE_PARENTS_CONTACTS = 4;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -121,6 +127,37 @@
public static final int SHOW_IN_SETTINGS_NO = 2;
/**
+ * Possible values for whether (and from whom) to inherit select user restrictions
+ * or device policies.
+ *
+ * @hide
+ */
+ @IntDef(prefix = "INHERIT_DEVICE_POLICY", value = {
+ INHERIT_DEVICE_POLICY_NO,
+ INHERIT_DEVICE_POLICY_FROM_PARENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InheritDevicePolicy {
+ }
+ /**
+ * Suggests that the given user profile should not inherit user restriction or device policy
+ * from any other user. This is the default value for any new user type.
+ * @hide
+ */
+ public static final int INHERIT_DEVICE_POLICY_NO = 0;
+ /**
+ * Suggests that the given user profile should inherit select user restrictions or
+ * device policies from its parent profile.
+ *
+ *<p> All the user restrictions and device policies would be not propagated to the profile
+ * with this property value. The {(TODO:b/256978256) @link DevicePolicyEngine}
+ * uses this property to determine and propagate only select ones to the given profile.
+ *
+ * @hide
+ */
+ public static final int INHERIT_DEVICE_POLICY_FROM_PARENT = 1;
+
+ /**
* Reference to the default user properties for this user's user type.
* <li>If non-null, then any absent property will use the default property from here instead.
* <li>If null, then any absent property indicates that the caller lacks permission to see it,
@@ -161,10 +198,12 @@
if (exposeAllFields) {
// Add items that require exposeAllFields to be true (strictest permission level).
setStartWithParent(orig.getStartWithParent());
+ setInheritDevicePolicy(orig.getInheritDevicePolicy());
}
if (hasManagePermission) {
// Add items that require MANAGE_USERS or stronger.
setShowInSettings(orig.getShowInSettings());
+ setUseParentsContacts(orig.getUseParentsContacts());
}
if (hasQueryOrManagePermission) {
// Add items that require QUERY_USERS or stronger.
@@ -261,6 +300,60 @@
}
private boolean mStartWithParent;
+ /**
+ * Return whether, and how, select user restrictions or device policies should be inherited
+ * from other user.
+ *
+ * Possible return values include
+ * {@link #INHERIT_DEVICE_POLICY_FROM_PARENT} or {@link #INHERIT_DEVICE_POLICY_NO}
+ *
+ * @hide
+ */
+ public @InheritDevicePolicy int getInheritDevicePolicy() {
+ if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) return mInheritDevicePolicy;
+ if (mDefaultProperties != null) return mDefaultProperties.mInheritDevicePolicy;
+ throw new SecurityException("You don't have permission to query inheritDevicePolicy");
+ }
+ /** @hide */
+ public void setInheritDevicePolicy(@InheritDevicePolicy int val) {
+ this.mInheritDevicePolicy = val;
+ setPresent(INDEX_INHERIT_DEVICE_POLICY);
+ }
+ private @InheritDevicePolicy int mInheritDevicePolicy;
+
+ /**
+ * Returns whether the current user must use parent user's contacts. If true, writes to the
+ * ContactsProvider corresponding to the current user will be disabled and reads will be
+ * redirected to the parent.
+ *
+ * This only applies to users that have parents (i.e. profiles) and is used to ensure
+ * they can access contacts from the parent profile. This will be generally inapplicable for
+ * non-profile users.
+ *
+ * Please note that in case of the clone profiles, only the allow-listed apps would be allowed
+ * to access contacts across profiles and other apps will not see any contacts.
+ * TODO(b/256126819) Add link to the method returning apps allow-listed for app-cloning
+ *
+ * @return whether contacts access from an associated profile is enabled for the user
+ * @hide
+ */
+ public boolean getUseParentsContacts() {
+ if (isPresent(INDEX_USE_PARENTS_CONTACTS)) return mUseParentsContacts;
+ if (mDefaultProperties != null) return mDefaultProperties.mUseParentsContacts;
+ throw new SecurityException("You don't have permission to query useParentsContacts");
+ }
+ /** @hide */
+ public void setUseParentsContacts(boolean val) {
+ this.mUseParentsContacts = val;
+ setPresent(INDEX_USE_PARENTS_CONTACTS);
+ }
+ /**
+ * Indicates whether the current user should use parent user's contacts.
+ * If this property is set true, the user will be blocked from storing any contacts in its
+ * own contacts database and will serve all read contacts calls through the parent's contacts.
+ */
+ private boolean mUseParentsContacts;
+
@Override
public String toString() {
// Please print in increasing order of PropertyIndex.
@@ -269,6 +362,8 @@
+ ", mShowInLauncher=" + getShowInLauncher()
+ ", mStartWithParent=" + getStartWithParent()
+ ", mShowInSettings=" + getShowInSettings()
+ + ", mInheritDevicePolicy=" + getInheritDevicePolicy()
+ + ", mUseParentsContacts=" + getUseParentsContacts()
+ "}";
}
@@ -283,6 +378,8 @@
pw.println(prefix + " mShowInLauncher=" + getShowInLauncher());
pw.println(prefix + " mStartWithParent=" + getStartWithParent());
pw.println(prefix + " mShowInSettings=" + getShowInSettings());
+ pw.println(prefix + " mInheritDevicePolicy=" + getInheritDevicePolicy());
+ pw.println(prefix + " mUseParentsContacts=" + getUseParentsContacts());
}
/**
@@ -324,6 +421,13 @@
break;
case ATTR_SHOW_IN_SETTINGS:
setShowInSettings(parser.getAttributeInt(i));
+ break;
+ case ATTR_INHERIT_DEVICE_POLICY:
+ setInheritDevicePolicy(parser.getAttributeInt(i));
+ break;
+ case ATTR_USE_PARENTS_CONTACTS:
+ setUseParentsContacts(parser.getAttributeBoolean(i));
+ break;
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -350,6 +454,14 @@
if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
}
+ if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) {
+ serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY,
+ mInheritDevicePolicy);
+ }
+ if (isPresent(INDEX_USE_PARENTS_CONTACTS)) {
+ serializer.attributeBoolean(null, ATTR_USE_PARENTS_CONTACTS,
+ mUseParentsContacts);
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -359,6 +471,8 @@
dest.writeInt(mShowInLauncher);
dest.writeBoolean(mStartWithParent);
dest.writeInt(mShowInSettings);
+ dest.writeInt(mInheritDevicePolicy);
+ dest.writeBoolean(mUseParentsContacts);
}
/**
@@ -372,6 +486,8 @@
mShowInLauncher = source.readInt();
mStartWithParent = source.readBoolean();
mShowInSettings = source.readInt();
+ mInheritDevicePolicy = source.readInt();
+ mUseParentsContacts = source.readBoolean();
}
@Override
@@ -399,6 +515,8 @@
private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
private boolean mStartWithParent = false;
private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
+ private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
+ private boolean mUseParentsContacts = false;
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
@@ -416,12 +534,26 @@
return this;
}
+ /** Sets the value for {@link #mInheritDevicePolicy}*/
+ public Builder setInheritDevicePolicy(
+ @InheritDevicePolicy int inheritRestrictionsDevicePolicy) {
+ mInheritDevicePolicy = inheritRestrictionsDevicePolicy;
+ return this;
+ }
+
+ public Builder setUseParentsContacts(boolean useParentsContacts) {
+ mUseParentsContacts = useParentsContacts;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated. */
public UserProperties build() {
return new UserProperties(
mShowInLauncher,
mStartWithParent,
- mShowInSettings);
+ mShowInSettings,
+ mInheritDevicePolicy,
+ mUseParentsContacts);
}
} // end Builder
@@ -429,11 +561,15 @@
private UserProperties(
@ShowInLauncher int showInLauncher,
boolean startWithParent,
- @ShowInSettings int showInSettings) {
+ @ShowInSettings int showInSettings,
+ @InheritDevicePolicy int inheritDevicePolicy,
+ boolean useParentsContacts) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
setStartWithParent(startWithParent);
setShowInSettings(showInSettings);
+ setInheritDevicePolicy(inheritDevicePolicy);
+ setUseParentsContacts(useParentsContacts);
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index ff07291..09d24d4 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -40,8 +40,10 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer;
import android.icu.text.PluralRules;
+import android.net.Uri;
import android.os.Build;
import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -59,6 +61,8 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -799,7 +803,21 @@
private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
@NonNull Resources wrapper, @NonNull TypedValue value) {
ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
- wrapper, value);
+ wrapper, value);
+ try {
+ return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
+ } catch (IOException ioe) {
+ // This is okay. This may be something that ImageDecoder does not
+ // support, like SVG.
+ return null;
+ }
+ }
+
+ @Nullable
+ private Drawable decodeImageDrawable(@NonNull FileInputStream fis, @NonNull Resources wrapper) {
+ ImageDecoder.Source src = ImageDecoder.createSource(wrapper, fis);
try {
return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
@@ -860,6 +878,17 @@
} else {
dr = loadXmlDrawable(wrapper, value, id, density, file);
}
+ } else if (file.startsWith("frro://")) {
+ Uri uri = Uri.parse(file);
+ File f = new File('/' + uri.getHost() + uri.getPath());
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(f,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ AssetFileDescriptor afd = new AssetFileDescriptor(
+ pfd,
+ Long.parseLong(uri.getQueryParameter("offset")),
+ Long.parseLong(uri.getQueryParameter("size")));
+ FileInputStream is = afd.createInputStream();
+ dr = decodeImageDrawable(is, wrapper);
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index c9a0626..04d57ad 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -22,7 +22,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.IntentSender;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -84,8 +86,11 @@
ICancellationSignal cancelRemote = null;
try {
- cancelRemote = mService.executeGetCredential(request,
- new GetCredentialTransport(executor, callback), mContext.getOpPackageName());
+ cancelRemote = mService.executeGetCredential(
+ request,
+ // TODO: use a real activity instead of context.
+ new GetCredentialTransport(mContext, executor, callback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -124,7 +129,8 @@
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeCreateCredential(request,
- new CreateCredentialTransport(executor, callback),
+ // TODO: use a real activity instead of context.
+ new CreateCredentialTransport(mContext, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -176,17 +182,30 @@
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
+ private final Context mActivityContext;
private final Executor mExecutor;
private final OutcomeReceiver<
GetCredentialResponse, CredentialManagerException> mCallback;
- private GetCredentialTransport(Executor executor,
+ private GetCredentialTransport(Context activityContext, Executor executor,
OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
+ mActivityContext = activityContext;
mExecutor = executor;
mCallback = callback;
}
@Override
+ public void onPendingIntent(PendingIntent pendingIntent) {
+ try {
+ mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "startIntentSender() failed for intent:"
+ + pendingIntent.getIntentSender(), e);
+ // TODO: propagate the error.
+ }
+ }
+
+ @Override
public void onResponse(GetCredentialResponse response) {
mExecutor.execute(() -> mCallback.onResult(response));
}
@@ -201,17 +220,30 @@
private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
+ private final Context mActivityContext;
private final Executor mExecutor;
private final OutcomeReceiver<
CreateCredentialResponse, CredentialManagerException> mCallback;
- private CreateCredentialTransport(Executor executor,
+ private CreateCredentialTransport(Context activityContext, Executor executor,
OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
+ mActivityContext = activityContext;
mExecutor = executor;
mCallback = callback;
}
@Override
+ public void onPendingIntent(PendingIntent pendingIntent) {
+ try {
+ mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "startIntentSender() failed for intent:"
+ + pendingIntent.getIntentSender(), e);
+ // TODO: propagate the error.
+ }
+ }
+
+ @Override
public void onResponse(CreateCredentialResponse response) {
mExecutor.execute(() -> mCallback.onResult(response));
}
diff --git a/core/java/android/credentials/ICreateCredentialCallback.aidl b/core/java/android/credentials/ICreateCredentialCallback.aidl
index 75620fa..87fd36f 100644
--- a/core/java/android/credentials/ICreateCredentialCallback.aidl
+++ b/core/java/android/credentials/ICreateCredentialCallback.aidl
@@ -16,6 +16,7 @@
package android.credentials;
+import android.app.PendingIntent;
import android.credentials.CreateCredentialResponse;
/**
@@ -24,6 +25,7 @@
* @hide
*/
interface ICreateCredentialCallback {
+ oneway void onPendingIntent(in PendingIntent pendingIntent);
oneway void onResponse(in CreateCredentialResponse response);
oneway void onError(int errorCode, String message);
}
\ No newline at end of file
diff --git a/core/java/android/credentials/IGetCredentialCallback.aidl b/core/java/android/credentials/IGetCredentialCallback.aidl
index 92e5851..da152ba 100644
--- a/core/java/android/credentials/IGetCredentialCallback.aidl
+++ b/core/java/android/credentials/IGetCredentialCallback.aidl
@@ -16,6 +16,7 @@
package android.credentials;
+import android.app.PendingIntent;
import android.credentials.GetCredentialResponse;
/**
@@ -24,6 +25,7 @@
* @hide
*/
interface IGetCredentialCallback {
+ oneway void onPendingIntent(in PendingIntent pendingIntent);
oneway void onResponse(in GetCredentialResponse response);
oneway void onError(int errorCode, String message);
}
\ No newline at end of file
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 98157d7..0444278 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -131,6 +131,13 @@
return this;
}
+ /** Sets the remote entry of the provider. */
+ @NonNull
+ public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+ mRemoteEntry = remoteEntry;
+ return this;
+ }
+
/** Builds a {@link CreateCredentialProviderData}. */
@NonNull
public CreateCredentialProviderData build() {
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 33427d3..5c07e6e 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -17,7 +17,10 @@
package android.credentials.ui;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.app.slice.Slice;
+import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -53,7 +56,8 @@
/** Below are only available for get flows. */
public static final String HINT_NOTE = "HINT_NOTE";
public static final String HINT_USER_NAME = "HINT_USER_NAME";
- public static final String HINT_CREDENTIAL_TYPE = "HINT_CREDENTIAL_TYPE";
+ public static final String HINT_CREDENTIAL_TYPE_DISPLAY_NAME =
+ "HINT_CREDENTIAL_TYPE_DISPLAY_NAME";
public static final String HINT_PASSKEY_USER_DISPLAY_NAME = "HINT_PASSKEY_USER_DISPLAY_NAME";
public static final String HINT_PASSWORD_VALUE = "HINT_PASSWORD_VALUE";
@@ -84,6 +88,8 @@
@NonNull private final String mKey;
@NonNull private final String mSubkey;
+ @Nullable private PendingIntent mPendingIntent;
+ @Nullable private Intent mFrameworkExtrasIntent;
@NonNull
private final Slice mSlice;
@@ -99,14 +105,29 @@
AnnotationValidations.validate(NonNull.class, null, mSubkey);
mSlice = slice;
AnnotationValidations.validate(NonNull.class, null, mSlice);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
}
+ /** Constructor to be used for an entry that does not require further activities
+ * to be invoked when selected.
+ */
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
mKey = key;
mSubkey = subkey;
mSlice = slice;
}
+ /** Constructor to be used for an entry that requires a pending intent to be invoked
+ * when clicked.
+ */
+ public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+ @NonNull PendingIntent pendingIntent, @Nullable Intent intent) {
+ this(key, subkey, slice);
+ mPendingIntent = pendingIntent;
+ mFrameworkExtrasIntent = intent;
+ }
+
/**
* Returns the identifier of this entry that's unique within the context of the CredentialManager
* request.
@@ -132,11 +153,23 @@
return mSlice;
}
+ @Nullable
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ @Nullable
+ public Intent getFrameworkExtrasIntent() {
+ return mFrameworkExtrasIntent;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mKey);
dest.writeString8(mSubkey);
mSlice.writeToParcel(dest, flags);
+ mPendingIntent.writeToParcel(dest, flags);
+ mFrameworkExtrasIntent.writeToParcel(dest, flags);
}
@Override
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 4751696..b608e65 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.content.res.Resources;
import android.os.Parcel;
import android.os.ResultReceiver;
@@ -36,9 +37,10 @@
ArrayList<DisabledProviderData> disabledProviderDataList,
ResultReceiver resultReceiver) {
Intent intent = new Intent();
- // TODO: define these as proper config strings.
- String activityName = "com.android.credentialmanager/.CredentialSelectorActivity";
- intent.setComponent(ComponentName.unflattenFromString(activityName));
+ ComponentName componentName = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(
+ com.android.internal.R.string.config_credentialManagerDialogComponent));
+ intent.setComponent(componentName);
intent.putParcelableArrayListExtra(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
new file mode 100644
index 0000000..420956f
--- /dev/null
+++ b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.credentials.ui;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Response from a provider's pending intent
+ *
+ * @hide
+ */
+public final class ProviderPendingIntentResponse implements Parcelable {
+ private final int mResultCode;
+ @Nullable
+ private final Intent mResultData;
+
+ public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
+ mResultCode = resultCode;
+ mResultData = resultData;
+ }
+
+ protected ProviderPendingIntentResponse(Parcel in) {
+ mResultCode = in.readInt();
+ mResultData = in.readTypedObject(Intent.CREATOR);
+ }
+
+ public static final Creator<ProviderPendingIntentResponse> CREATOR =
+ new Creator<ProviderPendingIntentResponse>() {
+ @Override
+ public ProviderPendingIntentResponse createFromParcel(Parcel in) {
+ return new ProviderPendingIntentResponse(in);
+ }
+
+ @Override
+ public ProviderPendingIntentResponse[] newArray(int size) {
+ return new ProviderPendingIntentResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeSerializable(mResultCode);
+ dest.writeTypedObject(mResultData, flags);
+ }
+
+ /** Returns the result code associated with this pending intent activity result. */
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /** Returns the result data associated with this pending intent activity result. */
+ @NonNull public Intent getResultData() {
+ return mResultData;
+ }
+}
diff --git a/core/java/android/credentials/ui/UserSelectionDialogResult.java b/core/java/android/credentials/ui/UserSelectionDialogResult.java
index 6025d78..0e8e7b6 100644
--- a/core/java/android/credentials/ui/UserSelectionDialogResult.java
+++ b/core/java/android/credentials/ui/UserSelectionDialogResult.java
@@ -57,6 +57,7 @@
@NonNull private final String mProviderId;
@NonNull private final String mEntryKey;
@NonNull private final String mEntrySubkey;
+ @Nullable private ProviderPendingIntentResponse mProviderPendingIntentResponse;
public UserSelectionDialogResult(
@NonNull IBinder requestToken, @NonNull String providerId,
@@ -67,6 +68,17 @@
mEntrySubkey = entrySubkey;
}
+ public UserSelectionDialogResult(
+ @NonNull IBinder requestToken, @NonNull String providerId,
+ @NonNull String entryKey, @NonNull String entrySubkey,
+ @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+ super(requestToken);
+ mProviderId = providerId;
+ mEntryKey = entryKey;
+ mEntrySubkey = entrySubkey;
+ mProviderPendingIntentResponse = providerPendingIntentResponse;
+ }
+
/** Returns provider package name whose entry was selected by the user. */
@NonNull
public String getProviderId() {
@@ -85,6 +97,12 @@
return mEntrySubkey;
}
+ /** Returns the pending intent response from the provider. */
+ @Nullable
+ public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+ return mProviderPendingIntentResponse;
+ }
+
protected UserSelectionDialogResult(@NonNull Parcel in) {
super(in);
String providerId = in.readString8();
@@ -97,6 +115,7 @@
AnnotationValidations.validate(NonNull.class, null, mEntryKey);
mEntrySubkey = entrySubkey;
AnnotationValidations.validate(NonNull.class, null, mEntrySubkey);
+ mProviderPendingIntentResponse = in.readTypedObject(ProviderPendingIntentResponse.CREATOR);
}
@Override
@@ -105,6 +124,7 @@
dest.writeString8(mProviderId);
dest.writeString8(mEntryKey);
dest.writeString8(mEntrySubkey);
+ dest.writeTypedObject(mProviderPendingIntentResponse, flags);
}
@Override
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index 24480e9..beb7f36 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -198,6 +198,15 @@
*/
public static final int RESULT_ERROR_INVALID_XML = -10007;
+ /**
+ * Indicates a failure due to invalid debug certificate file.
+ *
+ * This error code is only used with the shell command interaction.
+ *
+ * @hide
+ */
+ public static final int RESULT_ERROR_INVALID_DEBUG_CERTIFICATE = -10008;
+
private FontManager(@NonNull IFontManager iFontManager) {
mIFontManager = iFontManager;
}
diff --git a/core/java/android/hardware/CameraInfo.java b/core/java/android/hardware/CameraInfo.java
index 072be50..41ef6aa 100644
--- a/core/java/android/hardware/CameraInfo.java
+++ b/core/java/android/hardware/CameraInfo.java
@@ -60,4 +60,4 @@
return new CameraInfo[size];
}
};
-};
+}
diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java
index 874af29..fa35efb 100644
--- a/core/java/android/hardware/CameraStatus.java
+++ b/core/java/android/hardware/CameraStatus.java
@@ -68,4 +68,4 @@
return new CameraStatus[size];
}
};
-};
+}
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index d415706..267ef36 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -118,4 +118,4 @@
}
return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
}
-};
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index d3cb59d..f561278 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1310,6 +1310,22 @@
new Key<int[]>("android.control.availableSettingsOverrides", int[].class);
/**
+ * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}.</p>
+ * <p>Will be <code>false</code> if auto-framing is not available.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Boolean> CONTROL_AUTOFRAMING_AVAILABLE =
+ new Key<Boolean>("android.control.autoframingAvailable", boolean.class);
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 2a47851..bbdb626 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -802,6 +802,46 @@
}
/**
+ * Retrieve support for capture progress callbacks via
+ * {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureProcessProgressed}.
+ *
+ * @param extension the extension type
+ * @return {@code true} in case progress callbacks are supported, {@code false} otherwise
+ *
+ * @throws IllegalArgumentException in case of an unsupported extension.
+ */
+ public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
+ long clientId = registerClient(mContext);
+ if (clientId < 0) {
+ throw new IllegalArgumentException("Unsupported extensions");
+ }
+
+ try {
+ if (!isExtensionSupported(mCameraId, extension, mChars)) {
+ throw new IllegalArgumentException("Unsupported extension");
+ }
+
+ if (areAdvancedExtensionsSupported()) {
+ IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+ extender.init(mCameraId);
+ return extender.isCaptureProcessProgressAvailable();
+ } else {
+ Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
+ initializeExtension(extension);
+ extenders.second.init(mCameraId, mChars.getNativeMetadata());
+ return extenders.second.isCaptureProcessProgressAvailable();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
+ + " not respond!");
+ } finally {
+ unregisterClient(clientId);
+ }
+
+ return false;
+ }
+
+ /**
* Returns the set of keys supported by a {@link CaptureRequest} submitted in a
* {@link CameraExtensionSession} with a given extension type.
*
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
index 6ddaddf..6f895d5 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -16,6 +16,7 @@
package android.hardware.camera2;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import java.util.concurrent.Executor;
@@ -198,6 +199,41 @@
@NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
// default empty implementation
}
+
+ /**
+ * This method is called when image capture processing is ongoing between
+ * {@link #onCaptureProcessStarted} and the processed still capture frame returning
+ * to the client surface.
+ *
+ * <p>The value included in the arguments provides clients with an estimate
+ * of the post-processing progress which could take significantly more time
+ * relative to the rest of the {@link #capture} sequence.</p>
+ *
+ * <p>The callback will be triggered only by extensions that return {@code true}
+ * from calls
+ * {@link CameraExtensionCharacteristics#isCaptureProcessProgressAvailable}.</p>
+ *
+ * <p>If support for this callback is present, then clients will be notified at least once
+ * with progress value 100.</p>
+ *
+ * <p>The callback will be triggered only for still capture requests {@link #capture} and
+ * is not supported for repeating requests {@link #setRepeatingRequest}.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param session The session received during
+ * {@link StateCallback#onConfigured(CameraExtensionSession)}
+ * @param request The request that was given to the CameraDevice
+ * @param progress Value between 0 and 100 (inclusive) indicating the current
+ * post-processing progress
+ *
+ * @see CameraExtensionCharacteristics#isCaptureProcessProgressAvailable
+ *
+ */
+ public void onCaptureProcessProgressed(@NonNull CameraExtensionSession session,
+ @NonNull CaptureRequest request, @IntRange(from = 0, to = 100) int progress) {
+ // default empty implementation
+ }
}
/**
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 545aa8f..44f8b1b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3244,6 +3244,28 @@
public static final int CONTROL_SETTINGS_OVERRIDE_VENDOR_START = 0x4000;
//
+ // Enumeration values for CaptureRequest#CONTROL_AUTOFRAMING
+ //
+
+ /**
+ * <p>Disable autoframing.</p>
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ */
+ public static final int CONTROL_AUTOFRAMING_OFF = 0;
+
+ /**
+ * <p>Enable autoframing to keep people in the frame's field of view.</p>
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ */
+ public static final int CONTROL_AUTOFRAMING_ON = 1;
+
+ /**
+ * <p>Automatically select ON or OFF based on the system level preferences.</p>
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ */
+ public static final int CONTROL_AUTOFRAMING_AUTO = 2;
+
+ //
// Enumeration values for CaptureRequest#EDGE_MODE
//
@@ -3997,6 +4019,29 @@
public static final int CONTROL_AF_SCENE_CHANGE_DETECTED = 1;
//
+ // Enumeration values for CaptureResult#CONTROL_AUTOFRAMING_STATE
+ //
+
+ /**
+ * <p>Auto-framing is inactive.</p>
+ * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+ */
+ public static final int CONTROL_AUTOFRAMING_STATE_INACTIVE = 0;
+
+ /**
+ * <p>Auto-framing is in process - either zooming in, zooming out or pan is taking place.</p>
+ * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+ */
+ public static final int CONTROL_AUTOFRAMING_STATE_FRAMING = 1;
+
+ /**
+ * <p>Auto-framing has reached a stable state (frame/fov is not being adjusted). The state
+ * may transition back to FRAMING if the scene changes.</p>
+ * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+ */
+ public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2;
+
+ //
// Enumeration values for CaptureResult#FLASH_STATE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 407ea07..ea3e4a8 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2498,12 +2498,8 @@
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * <p><b>Limited capability</b> -
- * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
- * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_SETTINGS_OVERRIDE_OFF
* @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
*/
@@ -2513,6 +2509,42 @@
new Key<Integer>("android.control.settingsOverride", int.class);
/**
+ * <p>Automatic crop, pan and zoom to keep objects in the center of the frame.</p>
+ * <p>Auto-framing is a special mode provided by the camera device to dynamically crop, zoom
+ * or pan the camera feed to try to ensure that the people in a scene occupy a reasonable
+ * portion of the viewport. It is primarily designed to support video calling in
+ * situations where the user isn't directly in front of the device, especially for
+ * wide-angle cameras.
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in CaptureResult will be used
+ * to denote the coordinates of the auto-framed region.
+ * Zoom and video stabilization controls are disabled when auto-framing is enabled. The 3A
+ * regions must map the screen coordinates into the scaler crop returned from the capture
+ * result instead of using the active array sensor.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AUTOFRAMING_OFF OFF}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_ON ON}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_AUTO AUTO}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see #CONTROL_AUTOFRAMING_OFF
+ * @see #CONTROL_AUTOFRAMING_ON
+ * @see #CONTROL_AUTOFRAMING_AUTO
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_AUTOFRAMING =
+ new Key<Integer>("android.control.autoframing", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index c4f0cab..285c933 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2702,12 +2702,8 @@
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * <p><b>Limited capability</b> -
- * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
- * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_SETTINGS_OVERRIDE_OFF
* @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
*/
@@ -2717,6 +2713,79 @@
new Key<Integer>("android.control.settingsOverride", int.class);
/**
+ * <p>Automatic crop, pan and zoom to keep objects in the center of the frame.</p>
+ * <p>Auto-framing is a special mode provided by the camera device to dynamically crop, zoom
+ * or pan the camera feed to try to ensure that the people in a scene occupy a reasonable
+ * portion of the viewport. It is primarily designed to support video calling in
+ * situations where the user isn't directly in front of the device, especially for
+ * wide-angle cameras.
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in CaptureResult will be used
+ * to denote the coordinates of the auto-framed region.
+ * Zoom and video stabilization controls are disabled when auto-framing is enabled. The 3A
+ * regions must map the screen coordinates into the scaler crop returned from the capture
+ * result instead of using the active array sensor.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AUTOFRAMING_OFF OFF}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_ON ON}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_AUTO AUTO}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see #CONTROL_AUTOFRAMING_OFF
+ * @see #CONTROL_AUTOFRAMING_ON
+ * @see #CONTROL_AUTOFRAMING_AUTO
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_AUTOFRAMING =
+ new Key<Integer>("android.control.autoframing", int.class);
+
+ /**
+ * <p>Current state of auto-framing.</p>
+ * <p>When the camera doesn't have auto-framing available (i.e
+ * <code>{@link CameraCharacteristics#CONTROL_AUTOFRAMING_AVAILABLE android.control.autoframingAvailable}</code> == false) or it is not enabled (i.e
+ * <code>{@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}</code> == OFF), the state will always be INACTIVE.
+ * Other states indicate the current auto-framing state:</p>
+ * <ul>
+ * <li>When <code>{@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}</code> is set to ON, auto-framing will take
+ * place. While the frame is aligning itself to center the object (doing things like
+ * zooming in, zooming out or pan), the state will be FRAMING.</li>
+ * <li>When field of view is not being adjusted anymore and has reached a stable state, the
+ * state will be CONVERGED.</li>
+ * </ul>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AUTOFRAMING_STATE_INACTIVE INACTIVE}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_STATE_FRAMING FRAMING}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_STATE_CONVERGED CONVERGED}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ * @see CameraCharacteristics#CONTROL_AUTOFRAMING_AVAILABLE
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see #CONTROL_AUTOFRAMING_STATE_INACTIVE
+ * @see #CONTROL_AUTOFRAMING_STATE_FRAMING
+ * @see #CONTROL_AUTOFRAMING_STATE_CONVERGED
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_AUTOFRAMING_STATE =
+ new Key<Integer>("android.control.autoframingState", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
index 935a542..fa2cbe7 100644
--- a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
@@ -33,4 +33,5 @@
ISessionProcessorImpl getSessionProcessor();
CameraMetadataNative getAvailableCaptureRequestKeys(in String cameraId);
CameraMetadataNative getAvailableCaptureResultKeys(in String cameraId);
+ boolean isCaptureProcessProgressAvailable();
}
diff --git a/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl b/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl
index f3062ad..02a4690 100644
--- a/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl
+++ b/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl
@@ -27,4 +27,5 @@
void onCaptureSequenceCompleted(int captureSequenceId);
void onCaptureSequenceAborted(int captureSequenceId);
void onCaptureCompleted(long shutterTimestamp, int requestId, in CameraMetadataNative results);
+ void onCaptureProcessProgressed(int progress);
}
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 3a0c3a5..615536b 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -42,4 +42,5 @@
LatencyRange getEstimatedCaptureLatencyRange(in Size outputSize);
CameraMetadataNative getAvailableCaptureRequestKeys();
CameraMetadataNative getAvailableCaptureResultKeys();
+ boolean isCaptureProcessProgressAvailable();
}
diff --git a/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl b/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl
index 4114edb..57f94c0 100644
--- a/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl
@@ -21,4 +21,5 @@
interface IProcessResultImpl
{
void onCaptureCompleted(long shutterTimestamp, in CameraMetadataNative results);
+ void onCaptureProcessProgressed(int progress);
}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 01c1ef4..3a8dc03 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -663,6 +663,19 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ @Override
+ public void onCaptureProcessProgressed(int progress) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mClientCallbacks.onCaptureProcessProgressed(
+ CameraAdvancedExtensionSessionImpl.this, mClientRequest,
+ progress));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
private final class CaptureCallbackHandler extends CameraCaptureSession.CaptureCallback {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1f9f3b8..389c214 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1387,6 +1387,18 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ @Override
+ public void onCaptureProcessProgressed(int progress) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks.onCaptureProcessProgressed(CameraExtensionSessionImpl.this,
+ mClientRequest, progress));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
// This handler can operate in three modes:
diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
index 7b6a457..8304796 100644
--- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -26,6 +26,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.TreeMap;
/**
@@ -63,11 +64,11 @@
}
private void update() {
- Iterator iter = mFutureErrorMap.entrySet().iterator();
+ Iterator<Map.Entry<Long, Integer>> iter = mFutureErrorMap.entrySet().iterator();
while (iter.hasNext()) {
- TreeMap.Entry pair = (TreeMap.Entry)iter.next();
- Long errorFrameNumber = (Long)pair.getKey();
- int requestType = (int) pair.getValue();
+ Map.Entry<Long, Integer> pair = iter.next();
+ long errorFrameNumber = pair.getKey();
+ int requestType = pair.getValue();
Boolean removeError = false;
if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
removeError = true;
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
index 621a418..92a2fb6 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
@@ -103,6 +103,7 @@
return new MarshalerEnum(managedType, nativeType);
}
+ @SuppressWarnings("ReturnValueIgnored")
@Override
public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
if (nativeType == TYPE_INT32 || nativeType == TYPE_BYTE) {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index a0d8f8d..f4b87b9 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1344,7 +1344,8 @@
return false;
}
for (int j = 0; j < mSensorPixelModesUsed.size(); j++) {
- if (mSensorPixelModesUsed.get(j) != other.mSensorPixelModesUsed.get(j)) {
+ if (!Objects.equals(
+ mSensorPixelModesUsed.get(j), other.mSensorPixelModesUsed.get(j))) {
return false;
}
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index bdd45e6..dba1a5e 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -52,6 +52,22 @@
/** The maximum allowed device state identifier. */
public static final int MAXIMUM_DEVICE_STATE = 255;
+ /**
+ * Intent needed to launch the rear display overlay activity from SysUI
+ *
+ * @hide
+ */
+ public static final String ACTION_SHOW_REAR_DISPLAY_OVERLAY =
+ "com.android.intent.action.SHOW_REAR_DISPLAY_OVERLAY";
+
+ /**
+ * Intent extra sent to the rear display overlay activity of the current base state
+ *
+ * @hide
+ */
+ public static final String EXTRA_ORIGINAL_DEVICE_BASE_STATE =
+ "original_device_base_state";
+
private final DeviceStateManagerGlobal mGlobal;
/** @hide */
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 738045d..7756b9c 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -51,7 +51,7 @@
* connection with the device state service couldn't be established.
*/
@Nullable
- static DeviceStateManagerGlobal getInstance() {
+ public static DeviceStateManagerGlobal getInstance() {
synchronized (DeviceStateManagerGlobal.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
@@ -259,6 +259,22 @@
}
}
+ /**
+ * Provides notification to the system server that a device state feature overlay
+ * was dismissed. This should only be called from the {@link android.app.Activity} that
+ * was showing the overlay corresponding to the feature.
+ *
+ * Validation of there being an overlay visible and pending state request is handled on the
+ * system server.
+ */
+ public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+ try {
+ mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private void registerCallbackIfNeededLocked() {
if (mCallback == null) {
mCallback = new DeviceStateManagerCallback();
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 7175eae..0993160 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -103,4 +103,15 @@
@JavaPassthrough(annotation=
"@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
void cancelBaseStateOverride();
+
+ /**
+ * Notifies the system service that the educational overlay that was launched
+ * before entering a requested state was dismissed or closed, and provides
+ * the system information on if the pairing mode should be canceled or not.
+ *
+ * This should only be called from the overlay itself.
+ */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+ void onStateRequestOverlayDismissed(boolean shouldCancelRequest);
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6b3e673..1c2c895 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -412,7 +412,7 @@
* the PowerManagerService to focus on the global power state and not
* have to micro-manage screen off animations, auto-brightness and other effects.
*/
- public static final class DisplayPowerRequest {
+ public static class DisplayPowerRequest {
// Policy: Turn screen off as if the user pressed the power button
// including playing a screen off animation if applicable.
public static final int POLICY_OFF = 0;
diff --git a/core/java/android/hardware/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java
index 9ce834ca..c01c94c 100644
--- a/core/java/android/hardware/fingerprint/Fingerprint.java
+++ b/core/java/android/hardware/fingerprint/Fingerprint.java
@@ -69,4 +69,4 @@
return new Fingerprint[size];
}
};
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 3c73eb6..fb5ac5a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -49,6 +49,7 @@
import android.hardware.biometrics.BiometricTestSession;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.os.Binder;
import android.os.Build;
import android.os.CancellationSignal;
@@ -959,8 +960,14 @@
return;
}
+ final PointerContext pc = new PointerContext();
+ pc.x = (int) x;
+ pc.y = (int) y;
+ pc.minor = minor;
+ pc.major = major;
+
try {
- mService.onPointerDown(requestId, sensorId, x, y, minor, major);
+ mService.onPointerDown(requestId, sensorId, pc);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -976,8 +983,10 @@
return;
}
+ final PointerContext pc = new PointerContext();
+
try {
- mService.onPointerUp(requestId, sensorId);
+ mService.onPointerUp(requestId, sensorId, pc);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 365a6b3..6f35713 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -21,6 +21,7 @@
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -184,11 +185,11 @@
// Notifies about a finger touching the sensor area.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
+ void onPointerDown(long requestId, int sensorId, in PointerContext pc);
// Notifies about a finger leaving the sensor area.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void onPointerUp(long requestId, int sensorId);
+ void onPointerUp(long requestId, int sensorId, in PointerContext pc);
// Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled).
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 49c0f92..fd3d1ac 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -168,4 +168,9 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.BLUETOOTH)")
String getInputDeviceBluetoothAddress(int deviceId);
+
+ @EnforcePermission("MONITOR_INPUT")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MONITOR_INPUT)")
+ void pilferPointers(IBinder inputChannelToken);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 0cf15f7..702b39b 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1720,6 +1720,34 @@
}
/**
+ * Pilfer pointers from an input channel.
+ *
+ * Takes all the current pointer event streams that are currently being sent to the given
+ * input channel and generates appropriate cancellations for all other windows that are
+ * receiving these pointers.
+ *
+ * This API is intended to be used in conjunction with spy windows. When a spy window pilfers
+ * pointers, the foreground windows and all other spy windows that are receiving any of the
+ * pointers that are currently being dispatched to the pilfering window will have those pointers
+ * canceled. Only the pilfering window will continue to receive events for the affected pointers
+ * until the pointer is lifted.
+ *
+ * This method should be used with caution as unexpected pilfering can break fundamental user
+ * interactions.
+ *
+ * @see android.os.InputConfig#SPY
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_INPUT)
+ public void pilferPointers(IBinder inputChannelToken) {
+ try {
+ mIm.pilferPointers(inputChannelToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Adds a battery listener to be notified about {@link BatteryState} changes for an input
* device. The same listener can be registered for multiple input devices.
* The listener will be notified of the initial battery state of the device after it is
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
index c7450d8..ad51eb8 100644
--- a/core/java/android/hardware/input/VirtualTouchEvent.java
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -262,6 +262,16 @@
/**
* Sets the pressure of the event. This field is optional and can be omitted.
*
+ * @param pressure The pressure of the touch.
+ * Note: The VirtualTouchscreen, consuming VirtualTouchEvents, is
+ * configured with a pressure axis range from 0.0 to 255.0. Only the
+ * lower end of the range is enforced. You can pass values larger than
+ * 255.0. With physical input devices this could happen if the
+ * calibration is off. Values larger than 255.0 will not be trimmed and
+ * passed on as is.
+ *
+ * @throws IllegalArgumentException if the pressure is smaller than 0.
+ *
* @return this builder, to allow for chaining of calls
*/
public @NonNull Builder setPressure(@FloatRange(from = 0f) float pressure) {
diff --git a/core/java/android/hardware/radio/OWNERS b/core/java/android/hardware/radio/OWNERS
index d2bdd64..302fdd7 100644
--- a/core/java/android/hardware/radio/OWNERS
+++ b/core/java/android/hardware/radio/OWNERS
@@ -1,3 +1,4 @@
xuweilin@google.com
oscarazu@google.com
+ericjeong@google.com
keunyoung@google.com
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 5a44244..51236fe3 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -79,9 +79,20 @@
/* Returns true if the caller has permission to access the device. */
boolean hasDevicePermission(in UsbDevice device, String packageName);
+ /* Returns true if the given package/pid/uid has permission to access the device. */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+ boolean hasDevicePermissionWithIdentity(in UsbDevice device, String packageName,
+ int pid, int uid);
+
/* Returns true if the caller has permission to access the accessory. */
boolean hasAccessoryPermission(in UsbAccessory accessory);
+ /* Returns true if the given pid/uid has permission to access the accessory. */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+ boolean hasAccessoryPermissionWithIdentity(in UsbAccessory accessory, int pid, int uid);
+
/* Requests permission for the given package to access the device.
* Will display a system dialog to query the user if permission
* had not already been given.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 2c38f70..50dd0064 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -838,6 +838,28 @@
}
/**
+ * Returns true if the caller has permission to access the device. It's similar to the
+ * {@link #hasPermission(UsbDevice)} but allows to specify a different package/uid/pid.
+ *
+ * <p>Not for third-party apps.</p>
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ @RequiresFeature(PackageManager.FEATURE_USB_HOST)
+ public boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName,
+ int pid, int uid) {
+ if (mService == null) {
+ return false;
+ }
+ try {
+ return mService.hasDevicePermissionWithIdentity(device, packageName, pid, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns true if the caller has permission to access the accessory.
* Permission might have been granted temporarily via
* {@link #requestPermission(UsbAccessory, PendingIntent)} or
@@ -859,6 +881,27 @@
}
/**
+ * Returns true if the caller has permission to access the accessory. It's similar to the
+ * {@link #hasPermission(UsbAccessory)} but allows to specify a different uid/pid.
+ *
+ * <p>Not for third-party apps.</p>
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+ public boolean hasPermission(@NonNull UsbAccessory accessory, int pid, int uid) {
+ if (mService == null) {
+ return false;
+ }
+ try {
+ return mService.hasAccessoryPermissionWithIdentity(accessory, pid, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Requests temporary permission for the given package to access the device.
* This may result in a system dialog being displayed to the user
* if permission had not already been granted.
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index d23fb36..d55367f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -32,6 +32,7 @@
import android.util.Log;
import android.view.InputChannel;
import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
@@ -93,9 +94,10 @@
final int mTargetSdkVersion;
/**
- * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
- * so that {@link RemoteInputConnection} can query if {@link #unbindInput()} has already been
- * called or not, mainly to avoid unnecessary blocking operations.
+ * This is not {@code null} only between {@link #bindInput(InputBinding)} and
+ * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if
+ * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary
+ * blocking operations.
*
* <p>This field must be set and cleared only from the binder thread(s), where the system
* guarantees that {@link #bindInput(InputBinding)},
@@ -219,18 +221,26 @@
return;
case DO_SHOW_SOFT_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
+ final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.showSoftInputWithToken(
- msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
}
case DO_HIDE_SOFT_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
+ final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
- (IBinder) args.arg1);
+ (IBinder) args.arg1, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
@@ -416,16 +426,20 @@
@BinderThread
@Override
- public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
- flags, showInputToken, resultReceiver));
+ public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
+ flags, showInputToken, resultReceiver, statsToken));
}
@BinderThread
@Override
- public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
- flags, hideInputToken, resultReceiver));
+ public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
+ flags, hideInputToken, resultReceiver, statsToken));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index c6a3725..8759a6a 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -828,8 +828,7 @@
}
/**
- * Invokes {@code IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int,
- * CharSequence, TextAttribute)}.
+ * Replaces the specific range in the current input field with suggested text.
*
* @param start the character index where the replacement should start.
* @param end the character index where the replacement should end.
@@ -839,8 +838,9 @@
* that this means you can't position the cursor within the text.
* @param text the text to replace. This may include styles.
* @param textAttribute The extra information about the text. This value may be null.
- * @return {@code true} if the invocation is completed without {@link RemoteException}, {@code
- * false} otherwise.
+ * @return {@code true} if the specific range is replaced successfully, {@code false} otherwise.
+ * @see android.view.inputmethod.InputConnection#replaceText(int, int, CharSequence, int,
+ * TextAttribute)
*/
@AnyThread
public boolean replaceText(
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d902486..bf4fc4a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -124,6 +124,7 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import android.view.inputmethod.InputBinding;
@@ -669,6 +670,10 @@
*/
private IBinder mCurHideInputToken;
+ /** The token tracking the current IME request or {@code null} otherwise. */
+ @Nullable
+ private ImeTracker.Token mCurStatsToken;
+
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
onComputeInsets(mTmpInsets);
if (!mViewsCreated) {
@@ -870,10 +875,12 @@
@MainThread
@Override
public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder hideInputToken) {
+ IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
mSystemCallingHideSoftInput = true;
mCurHideInputToken = hideInputToken;
+ mCurStatsToken = statsToken;
hideSoftInput(flags, resultReceiver);
+ mCurStatsToken = null;
mCurHideInputToken = null;
mSystemCallingHideSoftInput = false;
}
@@ -884,6 +891,7 @@
@MainThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "hideSoftInput()");
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
&& !mSystemCallingHideSoftInput) {
@@ -918,12 +926,17 @@
@MainThread
@Override
public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder showInputToken) {
+ IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
mSystemCallingShowSoftInput = true;
mCurShowInputToken = showInputToken;
- showSoftInput(flags, resultReceiver);
- mCurShowInputToken = null;
- mSystemCallingShowSoftInput = false;
+ mCurStatsToken = statsToken;
+ try {
+ showSoftInput(flags, resultReceiver);
+ } finally {
+ mCurStatsToken = null;
+ mCurShowInputToken = null;
+ mSystemCallingShowSoftInput = false;
+ }
}
/**
@@ -932,6 +945,7 @@
@MainThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "showSoftInput()");
// TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
@@ -947,7 +961,12 @@
null /* icProto */);
final boolean wasVisible = isInputViewShown();
if (dispatchOnShowInputRequested(flags, false)) {
+ ImeTracker.get().onProgress(mCurStatsToken,
+ ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
showWindow(true);
+ } else {
+ ImeTracker.get().onFailed(mCurStatsToken,
+ ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
}
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -2923,8 +2942,10 @@
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
null /* icProto */);
+ ImeTracker.get().onProgress(mCurStatsToken,
+ ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
mPrivOps.applyImeVisibilityAsync(setVisible
- ? mCurShowInputToken : mCurHideInputToken, setVisible);
+ ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
}
private void finishViews(boolean finishingInput) {
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index f8e2558..d4b76c8 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -53,8 +53,8 @@
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -89,12 +89,10 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Prefix {}
- private static final HashMap<String, String> sPrefixLegacyFileNameMap =
- new HashMap<String, String>() {{
- put(PREFIX_XT, "netstats_xt.bin");
- put(PREFIX_UID, "netstats_uid.bin");
- put(PREFIX_UID_TAG, "netstats_uid.bin");
- }};
+ private static final Map<String, String> sPrefixLegacyFileNameMap = Map.of(
+ PREFIX_XT, "netstats_xt.bin",
+ PREFIX_UID, "netstats_uid.bin",
+ PREFIX_UID_TAG, "netstats_uid.bin");
// These version constants are copied from NetworkStatsCollection/History, which is okay for
// OEMs to modify to adapt their own logic.
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
index 4bc5b49..0427742 100644
--- a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
@@ -53,7 +53,7 @@
if (in.keySet().size() != EXPECTED_BUNDLE_KEY_CNT) {
throw new IllegalArgumentException(
String.format(
- "Expect PersistableBundle to have %d element but found: %d",
+ "Expect PersistableBundle to have %d element but found: %s",
EXPECTED_BUNDLE_KEY_CNT, in.keySet()));
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 64c1211..3282d56 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -29,7 +29,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.nfc.tech.MifareClassic;
@@ -525,66 +524,6 @@
}
/**
- * Helper to check if this device has FEATURE_NFC_BEAM, but without using
- * a context.
- * Equivalent to
- * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
- */
- private static boolean hasBeamFeature() {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm == null) {
- Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
- return false;
- }
- try {
- return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
- return false;
- }
- }
-
- /**
- * Helper to check if this device has FEATURE_NFC, but without using
- * a context.
- * Equivalent to
- * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
- */
- private static boolean hasNfcFeature() {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm == null) {
- Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
- return false;
- }
- try {
- return pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
- return false;
- }
- }
-
- /**
- * Helper to check if this device is NFC HCE capable, by checking for
- * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
- * but without using a context.
- */
- private static boolean hasNfcHceFeature() {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm == null) {
- Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
- return false;
- }
- try {
- return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
- || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
- return false;
- }
- }
-
- /**
* Return list of Secure Elements which support off host card emulation.
*
* @return List<String> containing secure elements on the device which supports
@@ -593,23 +532,21 @@
* @hide
*/
public @NonNull List<String> getSupportedOffHostSecureElements() {
+ if (mContext == null) {
+ throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ + " getSupportedOffHostSecureElements APIs");
+ }
List<String> offHostSE = new ArrayList<String>();
- IPackageManager pm = ActivityThread.getPackageManager();
+ PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
return offHostSE;
}
- try {
- if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC, 0)) {
- offHostSE.add("SIM");
- }
- if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE, 0)) {
- offHostSE.add("eSE");
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Package manager query failed, assuming no off-host CE feature", e);
- offHostSE.clear();
- return offHostSE;
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
+ offHostSE.add("SIM");
+ }
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
+ offHostSE.add("eSE");
}
return offHostSE;
}
@@ -621,10 +558,19 @@
*/
@UnsupportedAppUsage
public static synchronized NfcAdapter getNfcAdapter(Context context) {
+ if (context == null) {
+ if (sNullContextNfcAdapter == null) {
+ sNullContextNfcAdapter = new NfcAdapter(null);
+ }
+ return sNullContextNfcAdapter;
+ }
if (!sIsInitialized) {
- sHasNfcFeature = hasNfcFeature();
- sHasBeamFeature = hasBeamFeature();
- boolean hasHceFeature = hasNfcHceFeature();
+ PackageManager pm = context.getPackageManager();
+ sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+ sHasBeamFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM);
+ boolean hasHceFeature =
+ pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
/* is this device meant to have NFC */
if (!sHasNfcFeature && !hasHceFeature) {
Log.v(TAG, "this device does not have NFC support");
@@ -660,12 +606,6 @@
sIsInitialized = true;
}
- if (context == null) {
- if (sNullContextNfcAdapter == null) {
- sNullContextNfcAdapter = new NfcAdapter(null);
- }
- return sNullContextNfcAdapter;
- }
NfcAdapter adapter = sNfcAdapters.get(context);
if (adapter == null) {
adapter = new NfcAdapter(context);
@@ -676,8 +616,12 @@
/** get handle to NFC service interface */
private static INfcAdapter getServiceInterface() {
+ if (!sHasNfcFeature) {
+ /* NFC is not supported */
+ return null;
+ }
/* get a handle to NFC service */
- IBinder b = ServiceManager.getService("nfc");
+ IBinder b = ServiceManager.waitForService("nfc");
if (b == null) {
return null;
}
@@ -707,6 +651,15 @@
"context not associated with any application (using a mock context?)");
}
+ synchronized (NfcAdapter.class) {
+ if (!sIsInitialized) {
+ PackageManager pm = context.getPackageManager();
+ sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+ }
+ if (!sHasNfcFeature) {
+ return null;
+ }
+ }
if (getServiceInterface() == null) {
// NFC is not available
return null;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 0b56d19..6a42091 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -22,11 +22,9 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Activity;
-import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.nfc.INfcCardEmulation;
import android.nfc.NfcAdapter;
@@ -158,18 +156,13 @@
throw new UnsupportedOperationException();
}
if (!sIsInitialized) {
- IPackageManager pm = ActivityThread.getPackageManager();
+ PackageManager pm = context.getPackageManager();
if (pm == null) {
Log.e(TAG, "Cannot get PackageManager");
throw new UnsupportedOperationException();
}
- try {
- if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
- Log.e(TAG, "This device does not support card emulation");
- throw new UnsupportedOperationException();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "PackageManager query failed.");
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+ Log.e(TAG, "This device does not support card emulation");
throw new UnsupportedOperationException();
}
sIsInitialized = true;
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 3c92455..48bbf5b6 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -17,10 +17,8 @@
package android.nfc.cardemulation;
import android.app.Activity;
-import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.nfc.INfcFCardEmulation;
import android.nfc.NfcAdapter;
@@ -70,18 +68,13 @@
throw new UnsupportedOperationException();
}
if (!sIsInitialized) {
- IPackageManager pm = ActivityThread.getPackageManager();
+ PackageManager pm = context.getPackageManager();
if (pm == null) {
Log.e(TAG, "Cannot get PackageManager");
throw new UnsupportedOperationException();
}
- try {
- if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
- Log.e(TAG, "This device does not support NFC-F card emulation");
- throw new UnsupportedOperationException();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "PackageManager query failed.");
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
+ Log.e(TAG, "This device does not support NFC-F card emulation");
throw new UnsupportedOperationException();
}
sIsInitialized = true;
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
new file mode 100644
index 0000000..0009e61
--- /dev/null
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -0,0 +1,63 @@
+/*
+ * 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 android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the ART
+ * mainline module.
+ *
+ * Only the ART mainline module will be able to access an instance of this class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class ArtModuleServiceManager {
+ /** @hide */
+ public ArtModuleServiceManager() {}
+
+ /** A class that exposes the method to obtain each system service. */
+ public static final class ServiceRegisterer {
+ @NonNull private final String mServiceName;
+
+ /** @hide */
+ public ServiceRegisterer(@NonNull String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Returns the service from the service manager.
+ *
+ * If the service is not running, servicemanager will attempt to start it, and this function
+ * will wait for it to be ready.
+ *
+ * @return {@code null} only if there are permission problems or fatal errors.
+ */
+ @Nullable
+ public IBinder waitForService() {
+ return ServiceManager.waitForService(mServiceName);
+ }
+ }
+
+ /** Returns {@link ServiceRegisterer} for the "artd" service. */
+ @NonNull
+ public ServiceRegisterer getArtdServiceRegisterer() {
+ return new ServiceRegisterer("artd");
+ }
+}
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 97ec594..9bad0de 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -47,6 +47,14 @@
public abstract int getBatteryLevel();
/**
+ * Returns battery health status as an integer representing the current battery health constant.
+ *
+ * This is a simple accessor that's safe to be called from any locks, but internally it may
+ * wait on the battery service lock.
+ */
+ public abstract int getBatteryHealth();
+
+ /**
* Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct.
* Please note apparently it could be bigger than {@link #getBatteryFullCharge}.
*
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 6330661..1929a4d 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -536,8 +536,8 @@
mWarnOnBlocking = false;
warnOnBlocking = false;
- if (Build.IS_USERDEBUG) {
- // Log this as a WTF on userdebug builds.
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ // Log this as a WTF on userdebug and eng builds.
Log.wtf(Binder.TAG,
"Outgoing transactions from this process must be FLAG_ONEWAY",
new Throwable());
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 249f486..44a1fa5 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1446,6 +1446,28 @@
return IS_DEBUGGABLE;
}
+
+ /**
+ * Returns true if the device is running a secure build, such as "user" or "userdebug".
+ *
+ * Secure builds drop adbd privileges by default, though debuggable builds still allow users
+ * to gain root access via local shell. See should_drop_privileges() in adb for details.
+ * @hide
+ */
+ private static final boolean IS_SECURE =
+ SystemProperties.getBoolean("ro.secure", true);
+ /**
+ * Returns true if the device is running a secure build, such as "user" or "userdebug".
+ *
+ * Secure builds drop adbd privileges by default, though debuggable builds still allow users
+ * to gain root access via local shell. See should_drop_privileges() in adb for details.
+ * @hide
+ */
+ @TestApi
+ public static boolean isSecure() {
+ return IS_SECURE;
+ }
+
/** {@hide} */
public static final boolean IS_ENG = "eng".equals(TYPE);
/** {@hide} */
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index d5c3de1..b478a379 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -84,7 +84,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -1314,31 +1313,31 @@
private static long toBytes(long value, String unit) {
unit = unit.toUpperCase();
- if (List.of("B").contains(unit)) {
+ if ("B".equals(unit)) {
return value;
}
- if (List.of("K", "KB").contains(unit)) {
+ if ("K".equals(unit) || "KB".equals(unit)) {
return DataUnit.KILOBYTES.toBytes(value);
}
- if (List.of("M", "MB").contains(unit)) {
+ if ("M".equals(unit) || "MB".equals(unit)) {
return DataUnit.MEGABYTES.toBytes(value);
}
- if (List.of("G", "GB").contains(unit)) {
+ if ("G".equals(unit) || "GB".equals(unit)) {
return DataUnit.GIGABYTES.toBytes(value);
}
- if (List.of("KI", "KIB").contains(unit)) {
+ if ("KI".equals(unit) || "KIB".equals(unit)) {
return DataUnit.KIBIBYTES.toBytes(value);
}
- if (List.of("MI", "MIB").contains(unit)) {
+ if ("MI".equals(unit) || "MIB".equals(unit)) {
return DataUnit.MEBIBYTES.toBytes(value);
}
- if (List.of("GI", "GIB").contains(unit)) {
+ if ("GI".equals(unit) || "GIB".equals(unit)) {
return DataUnit.GIBIBYTES.toBytes(value);
}
@@ -1370,7 +1369,7 @@
sign = -1;
}
- fmtSize = fmtSize.replace(first + "", "");
+ fmtSize = fmtSize.substring(1);
}
int index = 0;
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 09bc4cc..0d1dde1 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -22,4 +22,5 @@
void updateTargetWorkDuration(long targetDurationNanos);
void reportActualWorkDuration(in long[] actualDurationNanos, in long[] timeStampNanos);
void close();
+ void sendHint(int hint);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 8eaa5ad..a887f2a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -130,7 +130,7 @@
boolean isUserRunning(int userId);
boolean isUserForeground(int userId);
boolean isUserVisible(int userId);
- List<UserHandle> getVisibleUsers();
+ int[] getVisibleUsers();
boolean isUserNameSet(int userId);
boolean hasRestrictedProfiles(int userId);
boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 9d8df7e..5c5af2a 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -73,4 +73,6 @@
# PermissionEnforcer
per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
-per-file PermissionEnforcer.java = file:/core/java/android/permission/OWNERS
+
+# ART
+per-file ArtModuleServiceManager.java = file:platform/art:/OWNERS
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index a75b5ef..86135bc 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
@@ -24,6 +25,10 @@
import com.android.internal.util.Preconditions;
import java.io.Closeable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
+
/** The PerformanceHintManager allows apps to send performance hint to system. */
@SystemService(Context.PERFORMANCE_HINT_SERVICE)
@@ -104,6 +109,40 @@
mNativeSessionPtr = nativeSessionPtr;
}
+ /**
+ * This hint indicates a sudden increase in CPU workload intensity. It means
+ * that this hint session needs extra CPU resources immediately to meet the
+ * target duration for the current work cycle.
+ */
+ public static final int CPU_LOAD_UP = 0;
+ /**
+ * This hint indicates a decrease in CPU workload intensity. It means that
+ * this hint session can reduce CPU resources and still meet the target duration.
+ */
+ public static final int CPU_LOAD_DOWN = 1;
+ /*
+ * This hint indicates an upcoming CPU workload that is completely changed and
+ * unknown. It means that the hint session should reset CPU resources to a known
+ * baseline to prepare for an arbitrary load, and must wake up if inactive.
+ */
+ public static final int CPU_LOAD_RESET = 2;
+ /*
+ * This hint indicates that the most recent CPU workload is resuming after a
+ * period of inactivity. It means that the hint session should allocate similar
+ * CPU resources to what was used previously, and must wake up if inactive.
+ */
+ public static final int CPU_LOAD_RESUME = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CPU_LOAD_"}, value = {
+ CPU_LOAD_UP,
+ CPU_LOAD_DOWN,
+ CPU_LOAD_RESET,
+ CPU_LOAD_RESUME
+ })
+ public @interface Hint {}
+
/** @hide */
@Override
protected void finalize() throws Throwable {
@@ -152,6 +191,21 @@
mNativeSessionPtr = 0;
}
}
+
+ /**
+ * Sends performance hints to inform the hint session of changes in the workload.
+ *
+ * @param hint The hint to send to the session.
+ */
+ public void sendHint(@Hint int hint) {
+ Preconditions.checkArgumentNonNegative(hint, "the hint ID should be at least"
+ + " zero.");
+ try {
+ nativeSendHint(mNativeSessionPtr, hint);
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
}
private static native long nativeAcquireManager();
@@ -163,4 +217,5 @@
private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
long actualDurationNanos);
private static native void nativeCloseSession(long nativeSessionPtr);
+ private static native void nativeSendHint(long nativeSessionPtr, int hint);
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e483328..ac1583a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -895,9 +895,21 @@
return isIsolated(myUid());
}
- /** {@hide} */
- @UnsupportedAppUsage
+ /**
+ * @deprecated Use {@link #isIsolatedUid(int)} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
public static final boolean isIsolated(int uid) {
+ return isIsolatedUid(uid);
+ }
+
+ /**
+ * Returns whether the process with the given {@code uid} is an isolated sandbox.
+ */
+ public static final boolean isIsolatedUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
|| (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e321a66..b6ff102 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -258,12 +258,14 @@
* waitForService should always be able to return the service.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
public static String[] getDeclaredInstances(@NonNull String iface) {
try {
return getIServiceManager().getDeclaredInstances(iface);
} catch (RemoteException e) {
Log.e(TAG, "error in getDeclaredInstances", e);
- return null;
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 6091bf9..bf72b1d 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.vibrator.IVibrator;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Range;
@@ -313,8 +314,14 @@
private static final float EPSILON = 1e-5f;
public MultiVibratorInfo(VibratorInfo[] vibrators) {
+ // Need to use an extra constructor to share the computation in super initialization.
+ this(vibrators, frequencyProfileIntersection(vibrators));
+ }
+
+ private MultiVibratorInfo(VibratorInfo[] vibrators,
+ VibratorInfo.FrequencyProfile mergedProfile) {
super(/* id= */ -1,
- capabilitiesIntersection(vibrators),
+ capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
supportedEffectsIntersection(vibrators),
supportedBrakingIntersection(vibrators),
supportedPrimitivesAndDurationsIntersection(vibrators),
@@ -323,14 +330,19 @@
integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
- frequencyProfileIntersection(vibrators));
+ mergedProfile);
}
- private static int capabilitiesIntersection(VibratorInfo[] infos) {
+ private static int capabilitiesIntersection(VibratorInfo[] infos,
+ boolean frequencyProfileIsEmpty) {
int intersection = ~0;
for (VibratorInfo info : infos) {
intersection &= info.getCapabilities();
}
+ if (frequencyProfileIsEmpty) {
+ // Revoke frequency control if the merged frequency profile ended up empty.
+ intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+ }
return intersection;
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 8bfa0e9..fb197f5 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -100,6 +100,7 @@
/** @hide */
public static final long TRACE_TAG_VIBRATOR = 1L << 23;
/** @hide */
+ @SystemApi(client = MODULE_LIBRARIES)
public static final long TRACE_TAG_AIDL = 1L << 24;
/** @hide */
public static final long TRACE_TAG_NNAPI = 1L << 25;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3d20d63..1f21bfe 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1506,6 +1506,30 @@
public static final String DISALLOW_CELLULAR_2G = "no_cellular_2g";
/**
+ * This user restriction specifies if Ultra-wideband is disallowed on the device. If
+ * Ultra-wideband is disallowed it cannot be turned on via Settings.
+ *
+ * <p>This restriction can only be set by a device owner or a profile owner of an
+ * organization-owned managed profile on the parent profile.
+ * In both cases, the restriction applies globally on the device and will turn off the
+ * ultra-wideband radio if it's currently on and prevent the radio from being turned on in
+ * the future.
+ *
+ * <p>
+ * Ultra-wideband (UWB) is a radio technology that can use a very low energy level
+ * for short-range, high-bandwidth communications over a large portion of the radio spectrum.
+ *
+ * <p>Default is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
+
+ /**
* List of key values that can be passed into the various user restriction related methods
* in {@link UserManager} & {@link DevicePolicyManager}.
* Note: This is slightly different from the real set of user restrictions listed in {@link
@@ -1587,6 +1611,7 @@
DISALLOW_WIFI_DIRECT,
DISALLOW_ADD_WIFI_CONFIG,
DISALLOW_CELLULAR_2G,
+ DISALLOW_ULTRA_WIDEBAND_RADIO,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
@@ -1612,6 +1637,16 @@
/** @hide */
public static final String SYSTEM_USER_MODE_EMULATION_HEADLESS = "headless";
+ /**
+ * System Property used to override whether users can be created even if their type is disabled
+ * or their limit is reached. Set value to 1 to enable.
+ *
+ * <p>Only used on non-user builds.
+ *
+ * @hide
+ */
+ public static final String DEV_CREATE_OVERRIDE_PROPERTY = "debug.user.creation_override";
+
private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
/**
@@ -2306,12 +2341,18 @@
}
/**
- * Used to check if the context user is the primary user. The primary user
- * is the first human user on a device. This is not supported in headless system user mode.
+ * Used to check if the context user is the primary user. The primary user is the first human
+ * user on a device. This is not supported in headless system user mode.
*
* @return whether the context user is the primary user.
+ *
+ * @deprecated This method always returns true for the system user, who may not be a full user
+ * if {@link #isHeadlessSystemUserMode} is true. Use {@link #isSystemUser}, {@link #isAdminUser}
+ * or {@link #isMainUser} instead.
+ *
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.MANAGE_USERS,
@@ -2336,6 +2377,29 @@
}
/**
+ * Returns true if the context user is the designated "main user" of the device. This user may
+ * have access to certain features which are limited to at most one user.
+ *
+ * <p>Currently, the first human user on the device will be the main user; in the future, the
+ * concept may be transferable, so a different user (or even no user at all) may be designated
+ * the main user instead.
+ *
+ * <p>Note that this will be the not be the system user on devices for which
+ * {@link #isHeadlessSystemUserMode()} returns true.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ @UserHandleAware
+ public boolean isMainUser() {
+ final UserInfo user = getUserInfo(mUserId);
+ return user != null && user.isMain();
+ }
+
+ /**
* Used to check if the context user is an admin user. An admin user is allowed to
* modify or configure certain settings that aren't available to non-admin users,
* create and delete additional users, etc. There can be more than one admin users.
@@ -2904,12 +2968,19 @@
*/
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS})
- public @NonNull List<UserHandle> getVisibleUsers() {
+ public @NonNull Set<UserHandle> getVisibleUsers() {
+ ArraySet<UserHandle> result = new ArraySet<>();
try {
- return mService.getVisibleUsers();
+ int[] visibleUserIds = mService.getVisibleUsers();
+ if (visibleUserIds != null) {
+ for (int userId : visibleUserIds) {
+ result.add(UserHandle.of(userId));
+ }
+ }
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
+ return result;
}
/**
@@ -4350,6 +4421,7 @@
* @return true if the creation of users of the given user type is enabled on this device.
* @hide
*/
+ @TestApi
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 8534c66..16ae3bc 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -77,8 +77,7 @@
List<SplitPermissionInfoParcelable> getSplitPermissions();
void startOneTimePermissionSession(String packageName, int userId, long timeout,
- long revokeAfterKilledDelay, int importanceToResetTimer,
- int importanceToKeepSessionAlive);
+ long revokeAfterKilledDelay);
@EnforcePermission("MANAGE_ONE_TIME_PERMISSION_SESSIONS")
void stopOneTimePermissionSession(String packageName, int userId);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 6b540d7..6769954 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1371,8 +1371,7 @@
@ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
try {
mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
- timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
- importanceToKeepSessionAlive);
+ timeoutMillis, revokeAfterKilledDelayMillis);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7095d1b..8a09cd7 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -25,9 +25,6 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.ActivityThread;
-import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
@@ -131,6 +128,13 @@
public static final String NAMESPACE_APP_STANDBY = "app_standby";
/**
+ * Namespace for all App Cloning related features.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final String NAMESPACE_APP_CLONING = "app_cloning";
+
+ /**
* Namespace for AttentionManagerService related features.
*
* @hide
@@ -875,9 +879,8 @@
@NonNull
@RequiresPermission(READ_DEVICE_CONFIG)
public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
return new Properties(namespace,
- Settings.Config.getStrings(contentResolver, namespace, Arrays.asList(names)));
+ Settings.Config.getStrings(namespace, Arrays.asList(names)));
}
/**
@@ -1016,8 +1019,7 @@
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean setProperty(@NonNull String namespace, @NonNull String name,
@Nullable String value, boolean makeDefault) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.putString(contentResolver, namespace, name, value, makeDefault);
+ return Settings.Config.putString(namespace, name, value, makeDefault);
}
/**
@@ -1038,8 +1040,7 @@
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
+ return Settings.Config.setStrings(properties.getNamespace(),
properties.mMap);
}
@@ -1055,8 +1056,7 @@
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.deleteString(contentResolver, namespace, name);
+ return Settings.Config.deleteString(namespace, name);
}
/**
@@ -1087,8 +1087,7 @@
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
+ Settings.Config.resetToDefaults(resetMode, namespace);
}
/**
@@ -1105,8 +1104,7 @@
*/
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- Settings.Config.setSyncDisabledMode(contentResolver, syncDisabledMode);
+ Settings.Config.setSyncDisabledMode(syncDisabledMode);
}
/**
@@ -1117,8 +1115,7 @@
*/
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static @SyncDisabledMode int getSyncDisabledMode() {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.getSyncDisabledMode(contentResolver);
+ return Settings.Config.getSyncDisabledMode();
}
/**
@@ -1141,8 +1138,7 @@
@NonNull String namespace,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
- enforceReadPermission(ActivityThread.currentApplication().getApplicationContext(),
- namespace);
+ enforceReadPermission(namespace);
synchronized (sLock) {
Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
if (oldNamespace == null) {
@@ -1209,7 +1205,7 @@
}
}
};
- ActivityThread.currentApplication().getContentResolver()
+ Settings.Config
.registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
}
@@ -1233,8 +1229,7 @@
sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
} else {
// Decrementing a namespace to zero means we no longer need its ContentObserver.
- ActivityThread.currentApplication().getContentResolver()
- .unregisterContentObserver(namespaceCount.first);
+ Settings.Config.unregisterContentObserver(namespaceCount.first);
sNamespaces.remove(namespace);
}
}
@@ -1274,8 +1269,8 @@
* Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
* @hide
*/
- public static void enforceReadPermission(Context context, String namespace) {
- if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ public static void enforceReadPermission(String namespace) {
+ if (Settings.Config.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
!= PackageManager.PERMISSION_GRANTED) {
if (!PUBLIC_NAMESPACES.contains(namespace)) {
throw new SecurityException("Permission denial: reading from settings requires:"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fab6f7b..b644a0b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -47,9 +47,11 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionName;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.database.SQLException;
import android.location.ILocationManager;
@@ -192,6 +194,21 @@
"android.settings.LOCATION_SCANNING_SETTINGS";
/**
+ * Activity Action: Show settings to manage creation/deletion of cloned apps.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_CLONED_APPS_SETTINGS =
+ "android.settings.MANAGE_CLONED_APPS_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of users.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -570,6 +587,22 @@
"android.settings.REQUEST_MANAGE_MEDIA";
/**
+ * Activity Action: Show settings to allow configuration of
+ * {@link Manifest.permission#RUN_LONG_JOBS} permission
+ *
+ * Input: Optionally, the Intent's data URI can specify the application package name to
+ * directly invoke the management GUI specific to the package name. For example
+ * "package:com.my.app".
+ * <p>
+ * Output: When a package data uri is passed as input, the activity result is set to
+ * {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise,
+ * the result is set to {@link android.app.Activity#RESULT_CANCELED}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_APP_LONG_JOBS =
+ "android.settings.MANAGE_APP_LONG_JOBS";
+
+ /**
* Activity Action: Show settings to allow configuration of cross-profile access for apps
*
* Input: Optionally, the Intent's data URI can specify the application package name to
@@ -675,6 +708,22 @@
"android.settings.WIFI_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of MTE.
+ * <p>
+ * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
+ * classes of security problems at a small runtime performance cost overhead.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MEMTAG_SETTINGS =
+ "android.settings.MEMTAG_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of a static IP
* address for Wi-Fi.
* <p>
@@ -3313,7 +3362,7 @@
public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
List<String> names) {
String namespace = prefix.substring(0, prefix.length() - 1);
- DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace);
+ DeviceConfig.enforceReadPermission(namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
@@ -10400,11 +10449,11 @@
public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
/**
- * The duration of timeout, in milliseconds, to switch from a non-primary user to the
- * primary user when the device is docked.
+ * The duration of timeout, in milliseconds, to switch from a non-Dock User to the
+ * Dock User when the device is docked.
* @hide
*/
- public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+ public static final String TIMEOUT_TO_DOCK_USER = "timeout_to_dock_user";
/**
* Backup manager behavioral parameters.
@@ -17971,20 +18020,36 @@
/**
* Look up a name in the database.
- * @param resolver to access the database with
* @param name to look up in the table
* @return the corresponding value, or null if not present
*
* @hide
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- static String getString(ContentResolver resolver, String name) {
+ static String getString(String name) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
}
/**
* Look up a list of names in the database, within the specified namespace.
*
+ * @param namespace to which the names belong
+ * @param names to look up in the table
+ * @return a non null, but possibly empty, map from name to value for any of the names that
+ * were found during lookup.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static Map<String, String> getStrings(@NonNull String namespace,
+ @NonNull List<String> names) {
+ return getStrings(getContentResolver(), namespace, names);
+ }
+
+ /**
+ * Look up a list of names in the database, within the specified namespace.
+ *
* @param resolver to access the database with
* @param namespace to which the names belong
* @param names to look up in the table
@@ -18022,7 +18087,6 @@
* <strong>not</strong> be set as the default.
* </p>
*
- * @param resolver to access the database with.
* @param namespace to store the name/value pair in.
* @param name to store.
* @param value to associate with the name.
@@ -18034,8 +18098,9 @@
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace,
+ public static boolean putString(@NonNull String namespace,
@NonNull String name, @Nullable String value, boolean makeDefault) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name),
value, null, makeDefault, resolver.getUserId(),
DEFAULT_OVERRIDEABLE_BY_RESTORE);
@@ -18045,6 +18110,23 @@
* Clear all name/value pairs for the provided namespace and save new name/value pairs in
* their place.
*
+ * @param namespace to which the names should be set.
+ * @param keyValues map of key names (without the prefix) to values.
+ * @return true if the name/value pairs were set, false if setting was blocked
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
+ public static boolean setStrings(@NonNull String namespace,
+ @NonNull Map<String, String> keyValues)
+ throws DeviceConfig.BadConfigException {
+ return setStrings(getContentResolver(), namespace, keyValues);
+ }
+
+ /**
+ * Clear all name/value pairs for the provided namespace and save new name/value pairs in
+ * their place.
+ *
* @param resolver to access the database with.
* @param namespace to which the names should be set.
* @param keyValues map of key names (without the prefix) to values.
@@ -18075,7 +18157,6 @@
/**
* Delete a name/value pair from the database for the specified namespace.
*
- * @param resolver to access the database with.
* @param namespace to delete the name/value pair from.
* @param name to delete.
* @return true if the value was deleted, false on database errors. If the name/value pair
@@ -18086,8 +18167,9 @@
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean deleteString(@NonNull ContentResolver resolver, @NonNull String namespace,
+ static boolean deleteString(@NonNull String namespace,
@NonNull String name) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.deleteStringForUser(resolver,
createCompositeName(namespace, name), resolver.getUserId());
}
@@ -18098,7 +18180,6 @@
* The method accepts an optional prefix parameter. If provided, only pairs with a name that
* starts with the exact prefix will be reset. Otherwise all will be reset.
*
- * @param resolver Handle to the content resolver.
* @param resetMode The reset mode to use.
* @param namespace Optionally, to limit which which namespace is reset.
*
@@ -18107,9 +18188,10 @@
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode,
+ static void resetToDefaults(@ResetMode int resetMode,
@Nullable String namespace) {
try {
+ ContentResolver resolver = getContentResolver();
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId());
arg.putInt(CALL_METHOD_RESET_MODE_KEY, resetMode);
@@ -18132,9 +18214,9 @@
*/
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void setSyncDisabledMode(
- @NonNull ContentResolver resolver, @SyncDisabledMode int disableSyncMode) {
+ static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
try {
+ ContentResolver resolver = getContentResolver();
Bundle args = new Bundle();
args.putInt(CALL_METHOD_SYNC_DISABLED_MODE_KEY, disableSyncMode);
IContentProvider cp = sProviderHolder.getProvider(resolver);
@@ -18153,8 +18235,9 @@
*/
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static int getSyncDisabledMode(@NonNull ContentResolver resolver) {
+ static int getSyncDisabledMode() {
try {
+ ContentResolver resolver = getContentResolver();
Bundle args = Bundle.EMPTY;
IContentProvider cp = sProviderHolder.getProvider(resolver);
Bundle bundle = cp.call(resolver.getAttributionSource(),
@@ -18171,7 +18254,6 @@
/**
* Register callback for monitoring Config table.
*
- * @param resolver Handle to the content resolver.
* @param callback callback to register
*
* @hide
@@ -18182,6 +18264,50 @@
registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback);
}
+
+ /**
+ * Register a content observer
+ *
+ * @hide
+ */
+ public static void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
+ @NonNull ContentObserver observer) {
+ ActivityThread.currentApplication().getContentResolver()
+ .registerContentObserver(uri, notifyForDescendants, observer);
+ }
+
+ /**
+ * Unregister a content observer
+ *
+ * @hide
+ */
+ public static void unregisterContentObserver(@NonNull ContentObserver observer) {
+ ActivityThread.currentApplication().getContentResolver()
+ .unregisterContentObserver(observer);
+ }
+
+ /**
+ * Determine whether the calling process of an IPC <em>or you</em> have been
+ * granted a particular permission. This is the same as
+ * {@link #checkCallingPermission}, except it grants your own permissions
+ * if you are not currently processing an IPC. Use with care!
+ *
+ * @param permission The name of the permission being checked.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the calling
+ * pid/uid is allowed that permission, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see PackageManager#checkPermission(String, String)
+ * @see #checkPermission
+ * @see #checkCallingPermission
+ * @hide
+ */
+ public static int checkCallingOrSelfPermission(@NonNull @PermissionName String permission) {
+ return ActivityThread.currentApplication()
+ .getApplicationContext().checkCallingOrSelfPermission(permission);
+ }
+
private static void registerMonitorCallbackAsUser(
@NonNull ContentResolver resolver, @UserIdInt int userHandle,
@NonNull RemoteCallback callback) {
@@ -18214,6 +18340,10 @@
Preconditions.checkNotNull(namespace);
return namespace + "/";
}
+
+ private static ContentResolver getContentResolver() {
+ return ActivityThread.currentApplication().getContentResolver();
+ }
}
/**
@@ -18643,6 +18773,9 @@
/**
* Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
* in Settings app on large screen devices.
+ *
+ * Developers should resolve the Intent action before using it.
+ *
* <p>
* Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
* specify the intent for the activity which will be embedded in Settings app.
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
index 2c382ef..c78fb1a 100644
--- a/core/java/android/security/keymaster/ExportResult.java
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -61,4 +61,4 @@
out.writeInt(resultCode);
out.writeByteArray(exportData);
}
-};
+}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 47b16a3..d2a4ae2 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -55,6 +55,20 @@
"android.service.controls.ControlsProviderService";
/**
+ * Manifest metadata to show a custom embedded activity as part of device controls.
+ *
+ * The value of this metadata must be the {@link ComponentName} as a string of an activity in
+ * the same package that will be launched as part of a TaskView.
+ *
+ * The activity must be exported, enabled and protected by
+ * {@link Manifest.permission.BIND_CONTROLS}.
+ *
+ * @hide
+ */
+ public static final String META_DATA_PANEL_ACTIVITY =
+ "android.service.controls.META_DATA_PANEL_ACTIVITY";
+
+ /**
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index e330d1e..f69dca8 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -33,13 +33,11 @@
* @hide
*/
public final class CreateCredentialResponse implements Parcelable {
- private final @Nullable CharSequence mHeader;
private final @NonNull List<SaveEntry> mSaveEntries;
private final @Nullable Action mRemoteSaveEntry;
//TODO : Add actions if needed
private CreateCredentialResponse(@NonNull Parcel in) {
- mHeader = in.readCharSequence();
List<SaveEntry> saveEntries = new ArrayList<>();
in.readTypedList(saveEntries, SaveEntry.CREATOR);
mSaveEntries = saveEntries;
@@ -48,7 +46,6 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mHeader);
dest.writeTypedList(mSaveEntries);
dest.writeTypedObject(mRemoteSaveEntry, flags);
}
@@ -72,21 +69,14 @@
};
/* package-private */ CreateCredentialResponse(
- @Nullable CharSequence header,
@NonNull List<SaveEntry> saveEntries,
@Nullable Action remoteSaveEntry) {
- this.mHeader = header;
this.mSaveEntries = saveEntries;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mSaveEntries);
this.mRemoteSaveEntry = remoteSaveEntry;
}
- /** Returns the header to be displayed on the UI. */
- public @Nullable CharSequence getHeader() {
- return mHeader;
- }
-
/** Returns the list of save entries to be displayed on the UI. */
public @NonNull List<SaveEntry> getSaveEntries() {
return mSaveEntries;
@@ -102,17 +92,9 @@
*/
@SuppressWarnings("WeakerAccess")
public static final class Builder {
-
- private @Nullable CharSequence mHeader;
private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
private @Nullable Action mRemoteSaveEntry;
- /** Sets the header to be displayed on the UI. */
- public @NonNull Builder setHeader(@Nullable CharSequence header) {
- mHeader = header;
- return this;
- }
-
/**
* Sets the list of save entries to be shown on the UI.
*
@@ -154,7 +136,6 @@
Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
+ "not be empty");
return new CreateCredentialResponse(
- mHeader,
mSaveEntries,
mRemoteSaveEntry);
}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 1d4ac25..98c537a 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -173,7 +173,7 @@
*/
public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
if (pendingIntent != null) {
- Preconditions.checkState(mCredential != null,
+ Preconditions.checkState(mCredential == null,
"credential is already set. Cannot set both the pendingIntent "
+ "and the credential");
}
@@ -189,7 +189,7 @@
*/
public @NonNull Builder setCredential(@Nullable Credential credential) {
if (credential != null) {
- Preconditions.checkState(mPendingIntent != null,
+ Preconditions.checkState(mPendingIntent == null,
"pendingIntent is already set. Cannot set both the "
+ "pendingIntent and the credential");
}
@@ -215,10 +215,10 @@
* is set, or if both are set.
*/
public @NonNull CredentialEntry build() {
- Preconditions.checkState(mPendingIntent == null && mCredential == null,
- "Either pendingIntent or credential must be set");
- Preconditions.checkState(mPendingIntent != null && mCredential != null,
- "Cannot set both the pendingIntent and credential");
+ Preconditions.checkState(((mPendingIntent != null && mCredential == null)
+ || (mPendingIntent == null && mCredential != null)),
+ "Either pendingIntent or credential must be set, and both cannot"
+ + "be set at the same time");
return new CredentialEntry(mType, mSlice, mPendingIntent,
mCredential, mAutoSelectAllowed);
}
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index 2c7a983..f89ad8e 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -24,7 +24,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -96,6 +95,8 @@
mLabel = mServiceInfo.loadSafeLabel(
mContext.getPackageManager(), 0 /* do not ellipsize */,
TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+ Log.i(TAG, "mLabel is : " + mLabel + ", for: " + mServiceInfo.getComponentName()
+ .flattenToString());
populateProviderCapabilities(context, serviceInfo);
}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index b1b08f4..24b7c3c 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -41,6 +41,27 @@
* @hide
*/
public abstract class CredentialProviderService extends Service {
+ /** Extra to be used by provider to populate the credential when ending the activity started
+ * through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
+ public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
+
+ /** Extra to be used by provider to populate the {@link CredentialsDisplayContent} when
+ * an authentication action entry is selected. **/
+ public static final String EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT =
+ "android.service.credentials.extra.GET_CREDENTIALS_DISPLAY_CONTENT";
+
+ /**
+ * Provider must read the value against this extra to receive the complete create credential
+ * request parameters, when a pending intent is launched.
+ */
+ public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
+
+ /** Extra to be used by the provider when setting the credential result. */
+ public static final String EXTRA_GET_CREDENTIAL =
+ "android.service.credentials.extra.GET_CREDENTIAL";
+
private static final String TAG = "CredProviderService";
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
@@ -64,7 +85,7 @@
}
@Override
- public final @NonNull IBinder onBind(@NonNull Intent intent) {
+ @NonNull public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
return mInterface.asBinder();
}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index ab5b524..4b23800 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -34,9 +34,6 @@
* @hide
*/
public final class CredentialsDisplayContent implements Parcelable {
- /** Header to be displayed on the UI. */
- private final @Nullable CharSequence mHeader;
-
/** List of credential entries to be displayed on the UI. */
private final @NonNull List<CredentialEntry> mCredentialEntries;
@@ -46,18 +43,15 @@
/** Remote credential entry to get the response from a different device. */
private final @Nullable Action mRemoteCredentialEntry;
- private CredentialsDisplayContent(@Nullable CharSequence header,
- @NonNull List<CredentialEntry> credentialEntries,
+ private CredentialsDisplayContent(@NonNull List<CredentialEntry> credentialEntries,
@NonNull List<Action> actions,
@Nullable Action remoteCredentialEntry) {
- mHeader = header;
mCredentialEntries = credentialEntries;
mActions = actions;
mRemoteCredentialEntry = remoteCredentialEntry;
}
private CredentialsDisplayContent(@NonNull Parcel in) {
- mHeader = in.readCharSequence();
List<CredentialEntry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
mCredentialEntries = credentialEntries;
@@ -87,20 +81,12 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mHeader);
dest.writeTypedList(mCredentialEntries, flags);
dest.writeTypedList(mActions, flags);
dest.writeTypedObject(mRemoteCredentialEntry, flags);
}
/**
- * Returns the header to be displayed on the UI.
- */
- public @Nullable CharSequence getHeader() {
- return mHeader;
- }
-
- /**
* Returns the list of credential entries to be displayed on the UI.
*/
public @NonNull List<CredentialEntry> getCredentialEntries() {
@@ -125,20 +111,11 @@
* Builds an instance of {@link CredentialsDisplayContent}.
*/
public static final class Builder {
- private CharSequence mHeader;
private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
private List<Action> mActions = new ArrayList<>();
private Action mRemoteCredentialEntry;
/**
- * Sets the header to be displayed on the UI.
- */
- public @NonNull Builder setHeader(@Nullable CharSequence header) {
- mHeader = header;
- return this;
- }
-
- /**
* Sets the remote credential entry to be displayed on the UI.
*/
public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
@@ -208,7 +185,7 @@
throw new IllegalStateException("credentialEntries and actions must not both "
+ "be empty");
}
- return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+ return new CredentialsDisplayContent(mCredentialEntries, mActions,
mRemoteCredentialEntry);
}
}
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index e06be44..03ba20e 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -119,9 +119,9 @@
*/
public @NonNull Builder setGetCredentialOptions(
@NonNull List<GetCredentialOption> getCredentialOptions) {
- Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+ Preconditions.checkCollectionNotEmpty(getCredentialOptions,
"getCredentialOptions");
- Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions,
+ Preconditions.checkCollectionElementsNotNull(getCredentialOptions,
"getCredentialOptions");
mGetCredentialOptions = getCredentialOptions;
return this;
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 5f30ad0..dd5373f 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -16,7 +16,6 @@
package android.service.dreams;
-import android.content.ComponentName;
/**
* Dream manager local system service interface.
@@ -61,17 +60,30 @@
public abstract boolean canStartDreaming(boolean isScreenOn);
/**
- * Called by the ActivityTaskManagerService to verify that the startDreamActivity
- * request comes from the current active dream component.
+ * Register a {@link DreamManagerStateListener}, which will be called when there are changes to
+ * dream state.
*
- * This function and its call path should not acquire the DreamManagerService lock
- * to avoid deadlock with the ActivityTaskManager lock.
- *
- * TODO: Make this interaction push-based - the DreamManager should inform the
- * ActivityTaskManager whenever the active dream component changes.
- *
- * @param doze If true returns the current active doze component. Otherwise, returns the
- * active dream component.
+ * @param listener The listener to register.
*/
- public abstract ComponentName getActiveDreamComponent(boolean doze);
+ public abstract void registerDreamManagerStateListener(DreamManagerStateListener listener);
+
+ /**
+ * Unregister a {@link DreamManagerStateListener}, which will be called when there are changes
+ * to dream state.
+ *
+ * @param listener The listener to unregister.
+ */
+ public abstract void unregisterDreamManagerStateListener(DreamManagerStateListener listener);
+
+ /**
+ * Called when there are changes to dream state.
+ */
+ public interface DreamManagerStateListener {
+ /**
+ * Called when keep dreaming when undocked has changed.
+ *
+ * @param keepDreaming True if the current dream should continue when undocking.
+ */
+ void onKeepDreamingWhenUndockedChanged(boolean keepDreaming);
+ }
}
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index aa45c20..6e8198b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -49,6 +49,17 @@
mShowComplications = shouldShowComplications;
onStartDream(layoutParams);
}
+
+ @Override
+ public void wakeUp() {
+ onWakeUp(() -> {
+ try {
+ mDreamOverlayCallback.onWakeUpComplete();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify dream of wakeUp:" + e);
+ }
+ });
+ }
};
IDreamOverlayCallback mDreamOverlayCallback;
@@ -71,6 +82,17 @@
public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
/**
+ * This method is overridden by implementations to handle when the dream has been requested
+ * to wakeup. This allows any overlay animations to run.
+ *
+ * @param onCompleteCallback The callback to trigger to notify the dream service that the
+ * overlay has completed waking up.
+ * @hide
+ */
+ public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+ }
+
+ /**
* This method is invoked to request the dream exit.
*/
public final void requestExit() {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 32bdf79..8b9852a 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -312,7 +312,14 @@
@Override
public void onExitRequested() {
// Simply finish dream when exit is requested.
- finish();
+ mHandler.post(() -> finish());
+ }
+
+ @Override
+ public void onWakeUpComplete() {
+ // Finish the dream once overlay animations are complete. Execute on handler since
+ // this is coming in on the overlay binder.
+ mHandler.post(() -> finish());
}
};
@@ -975,7 +982,18 @@
* </p>
*/
public void onWakeUp() {
- finish();
+ if (mOverlayConnection != null) {
+ mOverlayConnection.addConsumer(overlay -> {
+ try {
+ overlay.wakeUp();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error waking the overlay service", e);
+ finish();
+ }
+ });
+ } else {
+ finish();
+ }
}
/** {@inheritDoc} */
@@ -1294,7 +1312,7 @@
if (!mWindowless) {
Intent i = new Intent(this, DreamActivity.class);
i.setPackage(getApplicationContext().getPackageName());
- i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
final ServiceInfo serviceInfo = fetchServiceInfo(this,
new ComponentName(this, getClass()));
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 05ebbfe..7aeceb2c 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -38,4 +38,7 @@
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
in String dreamComponent, in boolean shouldShowComplications);
+
+ /** Called when the dream is waking, to do any exit animations */
+ void wakeUp();
}
diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
index ec76a33..4ad63f1 100644
--- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
@@ -28,4 +28,7 @@
* Invoked to request the dream exit.
*/
void onExitRequested();
+
+ /** Invoked when the dream overlay wakeUp animation is complete. */
+ void onWakeUpComplete();
}
\ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index cfc79e4..e821af1 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2365,6 +2365,7 @@
UserHandle user= (UserHandle) args.arg2;
NotificationChannel channel = (NotificationChannel) args.arg3;
int modificationType = (int) args.arg4;
+ args.recycle();
onNotificationChannelModified(pkgName, user, channel, modificationType);
} break;
@@ -2374,6 +2375,7 @@
UserHandle user = (UserHandle) args.arg2;
NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
int modificationType = (int) args.arg4;
+ args.recycle();
onNotificationChannelGroupModified(pkgName, user, group, modificationType);
} break;
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
index 714afee..e64bdd6 100644
--- a/core/java/android/service/timezone/TimeZoneProviderEvent.java
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -74,39 +74,53 @@
@Nullable
private final String mFailureCause;
- // Populated when mType == EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
+ // May be populated when EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
@Nullable
private final TimeZoneProviderStatus mTimeZoneProviderStatus;
- private TimeZoneProviderEvent(int type,
+ private TimeZoneProviderEvent(@EventType int type,
@ElapsedRealtimeLong long creationElapsedMillis,
@Nullable TimeZoneProviderSuggestion suggestion,
@Nullable String failureCause,
@Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
- mType = type;
+ mType = validateEventType(type);
mCreationElapsedMillis = creationElapsedMillis;
mSuggestion = suggestion;
mFailureCause = failureCause;
mTimeZoneProviderStatus = timeZoneProviderStatus;
+
+ // Confirm the type and the provider status agree.
+ if (mType == EVENT_TYPE_PERMANENT_FAILURE && mTimeZoneProviderStatus != null) {
+ throw new IllegalArgumentException(
+ "Unexpected status: mType=" + mType
+ + ", mTimeZoneProviderStatus=" + mTimeZoneProviderStatus);
+ }
+ }
+
+ private static @EventType int validateEventType(@EventType int eventType) {
+ if (eventType < EVENT_TYPE_PERMANENT_FAILURE || eventType > EVENT_TYPE_UNCERTAIN) {
+ throw new IllegalArgumentException(Integer.toString(eventType));
+ }
+ return eventType;
}
/** Returns an event of type {@link #EVENT_TYPE_SUGGESTION}. */
public static TimeZoneProviderEvent createSuggestionEvent(
@ElapsedRealtimeLong long creationElapsedMillis,
@NonNull TimeZoneProviderSuggestion suggestion,
- @NonNull TimeZoneProviderStatus providerStatus) {
+ @Nullable TimeZoneProviderStatus providerStatus) {
return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis,
- Objects.requireNonNull(suggestion), null, Objects.requireNonNull(providerStatus));
+ Objects.requireNonNull(suggestion), null, providerStatus);
}
/** Returns an event of type {@link #EVENT_TYPE_UNCERTAIN}. */
public static TimeZoneProviderEvent createUncertainEvent(
@ElapsedRealtimeLong long creationElapsedMillis,
- @NonNull TimeZoneProviderStatus timeZoneProviderStatus) {
+ @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
return new TimeZoneProviderEvent(
EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null,
- Objects.requireNonNull(timeZoneProviderStatus));
+ timeZoneProviderStatus);
}
/** Returns an event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
@@ -148,8 +162,8 @@
}
/**
- * Returns the status of the time zone provider. Populated when {@link #getType()} is {@link
- * #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}.
+ * Returns the status of the time zone provider. May be populated when {@link #getType()} is
+ * {@link #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}, otherwise {@code null}.
*/
@Nullable
public TimeZoneProviderStatus getTimeZoneProviderStatus() {
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index cd4a305..41ca94b 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -44,8 +44,8 @@
*
* <p>Once started, providers are expected to detect the time zone if possible, and report the
* result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link
- * #reportUncertain()}. Providers may also report that they have permanently failed
- * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
+ * #reportUncertain(TimeZoneProviderStatus)}. Providers may also report that they have permanently
+ * failed by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
* method for details.
*
* <p>After starting, providers are expected to issue their first callback within the timeout
@@ -203,7 +203,8 @@
* details.
*/
public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion) {
- reportSuggestion(suggestion, TimeZoneProviderStatus.UNKNOWN);
+ TimeZoneProviderStatus providerStatus = null;
+ reportSuggestionInternal(suggestion, providerStatus);
}
/**
@@ -212,11 +213,15 @@
*
* @param providerStatus provider status information that can influence detector service
* behavior and/or be reported via the device UI
- *
- * @hide
*/
public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
@NonNull TimeZoneProviderStatus providerStatus) {
+ Objects.requireNonNull(providerStatus);
+ reportSuggestionInternal(suggestion, providerStatus);
+ }
+
+ private void reportSuggestionInternal(@NonNull TimeZoneProviderSuggestion suggestion,
+ @Nullable TimeZoneProviderStatus providerStatus) {
Objects.requireNonNull(suggestion);
mHandler.post(() -> {
@@ -241,11 +246,13 @@
/**
* Indicates the time zone is not known because of an expected runtime state or error, e.g. when
- * the provider is unable to detect location, or there was a problem when resolving the location
- * to a time zone.
+ * the provider is unable to detect location, or there was connectivity issue.
+ *
+ * <p>See {@link #reportUncertain(TimeZoneProviderStatus)} for a more expressive version
*/
public final void reportUncertain() {
- reportUncertain(TimeZoneProviderStatus.UNKNOWN);
+ TimeZoneProviderStatus providerStatus = null;
+ reportUncertainInternal(providerStatus);
}
/**
@@ -256,10 +263,13 @@
*
* @param providerStatus provider status information that can influence detector service
* behavior and/or be reported via the device UI
- *
- * @hide
*/
public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
+ Objects.requireNonNull(providerStatus);
+ reportUncertainInternal(providerStatus);
+ }
+
+ private void reportUncertainInternal(@Nullable TimeZoneProviderStatus providerStatus) {
mHandler.post(() -> {
synchronized (mLock) {
ITimeZoneProviderManager manager = mManager;
@@ -349,8 +359,8 @@
* <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
* system server holds the latest report from the provider in memory. After an initial report,
* provider implementations are only required to send a report via {@link
- * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
- * differs from the previous report.
+ * #reportSuggestion(TimeZoneProviderSuggestion, TimeZoneProviderStatus)} or via {@link
+ * #reportUncertain(TimeZoneProviderStatus)} when it differs from the previous report.
*
* <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
* in rare cases, after which the provider should consider itself stopped and not make any
@@ -362,7 +372,8 @@
* Android system server may move on to use other providers or detection methods. Providers
* should therefore make best efforts during this time to generate a report, which could involve
* increased power usage. Providers should preferably report an explicit {@link
- * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+ * #reportUncertain(TimeZoneProviderStatus)} if the time zone(s) cannot be detected within the
+ * initialization timeout.
*
* @see #onStopUpdates() for the signal from the system server to stop sending reports
*/
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
index 87d7843..e0b78e9 100644
--- a/core/java/android/service/timezone/TimeZoneProviderStatus.java
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -18,14 +18,19 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Information about the status of a {@link TimeZoneProviderService}.
@@ -61,6 +66,7 @@
*
* @hide
*/
+@SystemApi
public final class TimeZoneProviderStatus implements Parcelable {
/**
@@ -71,7 +77,7 @@
@IntDef(prefix = "DEPENDENCY_STATUS_", value = {
DEPENDENCY_STATUS_UNKNOWN,
DEPENDENCY_STATUS_NOT_APPLICABLE,
- DEPENDENCY_STATUS_WORKING,
+ DEPENDENCY_STATUS_OK,
DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE,
DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT,
DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS,
@@ -81,14 +87,18 @@
@Retention(RetentionPolicy.SOURCE)
public @interface DependencyStatus {}
- /** The dependency's status is unknown. */
+ /**
+ * The dependency's status is unknown.
+ *
+ * @hide
+ */
public static final @DependencyStatus int DEPENDENCY_STATUS_UNKNOWN = 0;
/** The dependency is not used by the provider's implementation. */
public static final @DependencyStatus int DEPENDENCY_STATUS_NOT_APPLICABLE = 1;
- /** The dependency is applicable and working well. */
- public static final @DependencyStatus int DEPENDENCY_STATUS_WORKING = 2;
+ /** The dependency is applicable and there are no known problems. */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_OK = 2;
/**
* The dependency is used but is temporarily unavailable, e.g. connectivity has been lost for an
@@ -136,76 +146,105 @@
@IntDef(prefix = "OPERATION_STATUS_", value = {
OPERATION_STATUS_UNKNOWN,
OPERATION_STATUS_NOT_APPLICABLE,
- OPERATION_STATUS_WORKING,
+ OPERATION_STATUS_OK,
OPERATION_STATUS_FAILED,
})
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.SOURCE)
public @interface OperationStatus {}
- /** The operation's status is unknown. */
+ /**
+ * The operation's status is unknown.
+ *
+ * @hide
+ */
public static final @OperationStatus int OPERATION_STATUS_UNKNOWN = 0;
/** The operation is not used by the provider's implementation. */
public static final @OperationStatus int OPERATION_STATUS_NOT_APPLICABLE = 1;
- /** The operation is applicable and working well. */
- public static final @OperationStatus int OPERATION_STATUS_WORKING = 2;
+ /** The operation is applicable and there are no known problems. */
+ public static final @OperationStatus int OPERATION_STATUS_OK = 2;
- /** The operation is applicable and failed. */
+ /** The operation is applicable and it recently failed. */
public static final @OperationStatus int OPERATION_STATUS_FAILED = 3;
- /**
- * An instance that provides no information about status. Effectively a "null" status.
- */
- @NonNull
- public static final TimeZoneProviderStatus UNKNOWN = new TimeZoneProviderStatus(
- DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_UNKNOWN, OPERATION_STATUS_UNKNOWN);
-
- private final @DependencyStatus int mLocationDetectionStatus;
- private final @DependencyStatus int mConnectivityStatus;
- private final @OperationStatus int mTimeZoneResolutionStatus;
+ private final @DependencyStatus int mLocationDetectionDependencyStatus;
+ private final @DependencyStatus int mConnectivityDependencyStatus;
+ private final @OperationStatus int mTimeZoneResolutionOperationStatus;
private TimeZoneProviderStatus(
@DependencyStatus int locationDetectionStatus,
@DependencyStatus int connectivityStatus,
@OperationStatus int timeZoneResolutionStatus) {
- mLocationDetectionStatus = requireValidDependencyStatus(locationDetectionStatus);
- mConnectivityStatus = requireValidDependencyStatus(connectivityStatus);
- mTimeZoneResolutionStatus = requireValidOperationStatus(timeZoneResolutionStatus);
+ mLocationDetectionDependencyStatus = locationDetectionStatus;
+ mConnectivityDependencyStatus = connectivityStatus;
+ mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus;
}
/**
* Returns the status of the location detection dependencies used by the provider (where
* applicable).
*/
- public @DependencyStatus int getLocationDetectionStatus() {
- return mLocationDetectionStatus;
+ public @DependencyStatus int getLocationDetectionDependencyStatus() {
+ return mLocationDetectionDependencyStatus;
}
/**
* Returns the status of the connectivity dependencies used by the provider (where applicable).
*/
- public @DependencyStatus int getConnectivityStatus() {
- return mConnectivityStatus;
+ public @DependencyStatus int getConnectivityDependencyStatus() {
+ return mConnectivityDependencyStatus;
}
/**
* Returns the status of the time zone resolution operation used by the provider.
*/
- public @OperationStatus int getTimeZoneResolutionStatus() {
- return mTimeZoneResolutionStatus;
+ public @OperationStatus int getTimeZoneResolutionOperationStatus() {
+ return mTimeZoneResolutionOperationStatus;
}
@Override
public String toString() {
return "TimeZoneProviderStatus{"
- + "mLocationDetectionStatus=" + mLocationDetectionStatus
- + ", mConnectivityStatus=" + mConnectivityStatus
- + ", mTimeZoneResolutionStatus=" + mTimeZoneResolutionStatus
+ + "mLocationDetectionDependencyStatus="
+ + dependencyStatusToString(mLocationDetectionDependencyStatus)
+ + ", mConnectivityDependencyStatus="
+ + dependencyStatusToString(mConnectivityDependencyStatus)
+ + ", mTimeZoneResolutionOperationStatus="
+ + operationStatusToString(mTimeZoneResolutionOperationStatus)
+ '}';
}
+ /**
+ * Parses a {@link TimeZoneProviderStatus} from a toString() string for manual command-line
+ * testing.
+ *
+ * @hide
+ */
+ @NonNull
+ public static TimeZoneProviderStatus parseProviderStatus(@NonNull String arg) {
+ // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based
+ // on OpenJDK code.
+ Pattern pattern = Pattern.compile("TimeZoneProviderStatus\\{"
+ + "mLocationDetectionDependencyStatus=([^,]+)"
+ + ", mConnectivityDependencyStatus=([^,]+)"
+ + ", mTimeZoneResolutionOperationStatus=([^\\}]+)"
+ + "\\}");
+ Matcher matcher = pattern.matcher(arg);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Unable to parse provider status: " + arg);
+ }
+ @DependencyStatus int locationDependencyStatus =
+ dependencyStatusFromString(matcher.group(1));
+ @DependencyStatus int connectivityDependencyStatus =
+ dependencyStatusFromString(matcher.group(2));
+ @OperationStatus int timeZoneResolutionOperationStatus =
+ operationStatusFromString(matcher.group(3));
+ return new TimeZoneProviderStatus(locationDependencyStatus, connectivityDependencyStatus,
+ timeZoneResolutionOperationStatus);
+ }
+
public static final @NonNull Creator<TimeZoneProviderStatus> CREATOR = new Creator<>() {
@Override
public TimeZoneProviderStatus createFromParcel(Parcel in) {
@@ -229,9 +268,9 @@
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeInt(mLocationDetectionStatus);
- parcel.writeInt(mConnectivityStatus);
- parcel.writeInt(mTimeZoneResolutionStatus);
+ parcel.writeInt(mLocationDetectionDependencyStatus);
+ parcel.writeInt(mConnectivityDependencyStatus);
+ parcel.writeInt(mTimeZoneResolutionOperationStatus);
}
@Override
@@ -243,23 +282,33 @@
return false;
}
TimeZoneProviderStatus that = (TimeZoneProviderStatus) o;
- return mLocationDetectionStatus == that.mLocationDetectionStatus
- && mConnectivityStatus == that.mConnectivityStatus
- && mTimeZoneResolutionStatus == that.mTimeZoneResolutionStatus;
+ return mLocationDetectionDependencyStatus == that.mLocationDetectionDependencyStatus
+ && mConnectivityDependencyStatus == that.mConnectivityDependencyStatus
+ && mTimeZoneResolutionOperationStatus == that.mTimeZoneResolutionOperationStatus;
}
@Override
public int hashCode() {
return Objects.hash(
- mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+ mLocationDetectionDependencyStatus, mConnectivityDependencyStatus,
+ mTimeZoneResolutionOperationStatus);
+ }
+
+ /** @hide */
+ public boolean couldEnableTelephonyFallback() {
+ return mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+ || mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS
+ || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+ || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
}
/** A builder for {@link TimeZoneProviderStatus}. */
public static final class Builder {
- private @DependencyStatus int mLocationDetectionStatus = DEPENDENCY_STATUS_UNKNOWN;
- private @DependencyStatus int mConnectivityStatus = DEPENDENCY_STATUS_UNKNOWN;
- private @OperationStatus int mTimeZoneResolutionStatus = OPERATION_STATUS_UNKNOWN;
+ private @DependencyStatus int mLocationDetectionDependencyStatus =
+ DEPENDENCY_STATUS_UNKNOWN;
+ private @DependencyStatus int mConnectivityDependencyStatus = DEPENDENCY_STATUS_UNKNOWN;
+ private @OperationStatus int mTimeZoneResolutionOperationStatus = OPERATION_STATUS_UNKNOWN;
/**
* Creates a new builder instance. At creation time all status properties are set to
@@ -272,9 +321,9 @@
* @hide
*/
public Builder(TimeZoneProviderStatus toCopy) {
- mLocationDetectionStatus = toCopy.mLocationDetectionStatus;
- mConnectivityStatus = toCopy.mConnectivityStatus;
- mTimeZoneResolutionStatus = toCopy.mTimeZoneResolutionStatus;
+ mLocationDetectionDependencyStatus = toCopy.mLocationDetectionDependencyStatus;
+ mConnectivityDependencyStatus = toCopy.mConnectivityDependencyStatus;
+ mTimeZoneResolutionOperationStatus = toCopy.mTimeZoneResolutionOperationStatus;
}
/**
@@ -282,8 +331,9 @@
* See the {@code DEPENDENCY_STATUS_} constants for more information.
*/
@NonNull
- public Builder setLocationDetectionStatus(@DependencyStatus int locationDetectionStatus) {
- mLocationDetectionStatus = locationDetectionStatus;
+ public Builder setLocationDetectionDependencyStatus(
+ @DependencyStatus int locationDetectionStatus) {
+ mLocationDetectionDependencyStatus = locationDetectionStatus;
return this;
}
@@ -292,8 +342,8 @@
* See the {@code DEPENDENCY_STATUS_} constants for more information.
*/
@NonNull
- public Builder setConnectivityStatus(@DependencyStatus int connectivityStatus) {
- mConnectivityStatus = connectivityStatus;
+ public Builder setConnectivityDependencyStatus(@DependencyStatus int connectivityStatus) {
+ mConnectivityDependencyStatus = connectivityStatus;
return this;
}
@@ -302,8 +352,9 @@
* See the {@code OPERATION_STATUS_} constants for more information.
*/
@NonNull
- public Builder setTimeZoneResolutionStatus(@OperationStatus int timeZoneResolutionStatus) {
- mTimeZoneResolutionStatus = timeZoneResolutionStatus;
+ public Builder setTimeZoneResolutionOperationStatus(
+ @OperationStatus int timeZoneResolutionStatus) {
+ mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus;
return this;
}
@@ -313,11 +364,14 @@
@NonNull
public TimeZoneProviderStatus build() {
return new TimeZoneProviderStatus(
- mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+ requireValidDependencyStatus(mLocationDetectionDependencyStatus),
+ requireValidDependencyStatus(mConnectivityDependencyStatus),
+ requireValidOperationStatus(mTimeZoneResolutionOperationStatus));
}
}
- private @OperationStatus int requireValidOperationStatus(@OperationStatus int operationStatus) {
+ private static @OperationStatus int requireValidOperationStatus(
+ @OperationStatus int operationStatus) {
if (operationStatus < OPERATION_STATUS_UNKNOWN
|| operationStatus > OPERATION_STATUS_FAILED) {
throw new IllegalArgumentException(Integer.toString(operationStatus));
@@ -325,6 +379,45 @@
return operationStatus;
}
+ /** @hide */
+ @NonNull
+ public static String operationStatusToString(@OperationStatus int operationStatus) {
+ switch (operationStatus) {
+ case OPERATION_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case OPERATION_STATUS_NOT_APPLICABLE:
+ return "NOT_APPLICABLE";
+ case OPERATION_STATUS_OK:
+ return "OK";
+ case OPERATION_STATUS_FAILED:
+ return "FAILED";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + operationStatus);
+ }
+ }
+
+ /** @hide */
+ public static @OperationStatus int operationStatusFromString(
+ @Nullable String operationStatusString) {
+
+ if (TextUtils.isEmpty(operationStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + operationStatusString);
+ }
+
+ switch (operationStatusString) {
+ case "UNKNOWN":
+ return OPERATION_STATUS_UNKNOWN;
+ case "NOT_APPLICABLE":
+ return OPERATION_STATUS_NOT_APPLICABLE;
+ case "OK":
+ return OPERATION_STATUS_OK;
+ case "FAILED":
+ return OPERATION_STATUS_FAILED;
+ default:
+ throw new IllegalArgumentException("Unknown status: " + operationStatusString);
+ }
+ }
+
private static @DependencyStatus int requireValidDependencyStatus(
@DependencyStatus int dependencyStatus) {
if (dependencyStatus < DEPENDENCY_STATUS_UNKNOWN
@@ -333,4 +426,56 @@
}
return dependencyStatus;
}
+
+ /** @hide */
+ @NonNull
+ public static String dependencyStatusToString(@DependencyStatus int dependencyStatus) {
+ switch (dependencyStatus) {
+ case DEPENDENCY_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case DEPENDENCY_STATUS_NOT_APPLICABLE:
+ return "NOT_APPLICABLE";
+ case DEPENDENCY_STATUS_OK:
+ return "OK";
+ case DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE:
+ return "TEMPORARILY_UNAVAILABLE";
+ case DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT:
+ return "BLOCKED_BY_ENVIRONMENT";
+ case DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS:
+ return "DEGRADED_BY_SETTINGS";
+ case DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS:
+ return "BLOCKED_BY_SETTINGS";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + dependencyStatus);
+ }
+ }
+
+ /** @hide */
+ public static @DependencyStatus int dependencyStatusFromString(
+ @Nullable String dependencyStatusString) {
+
+ if (TextUtils.isEmpty(dependencyStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + dependencyStatusString);
+ }
+
+ switch (dependencyStatusString) {
+ case "UNKNOWN":
+ return DEPENDENCY_STATUS_UNKNOWN;
+ case "NOT_APPLICABLE":
+ return DEPENDENCY_STATUS_NOT_APPLICABLE;
+ case "OK":
+ return DEPENDENCY_STATUS_OK;
+ case "TEMPORARILY_UNAVAILABLE":
+ return DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
+ case "BLOCKED_BY_ENVIRONMENT":
+ return DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
+ case "DEGRADED_BY_SETTINGS":
+ return DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS;
+ case "BLOCKED_BY_SETTINGS":
+ return DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown status: " + dependencyStatusString);
+ }
+ }
}
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
index 1c4d14c91..bf8ee47 100644
--- a/core/java/android/service/voice/HotwordAudioStream.java
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -127,6 +127,16 @@
}
}
+ /**
+ * Provides an instance of {@link Builder} with state corresponding to this instance.
+ * @hide
+ */
+ public Builder buildUpon() {
+ return new Builder(mAudioFormat, mAudioStreamParcelFileDescriptor)
+ .setTimestamp(mTimestamp)
+ .setMetadata(mMetadata);
+ }
+
// Code below generated by codegen v1.0.23.
@@ -439,10 +449,10 @@
}
@DataClass.Generated(
- time = 1665976240224L,
+ time = 1666342101364L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java",
- inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static android.media.AudioTimestamp defaultTimestamp()\nprivate static android.os.PersistableBundle defaultMetadata()\nprivate java.lang.String timestampToString()\nprivate void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+ inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static android.media.AudioTimestamp defaultTimestamp()\nprivate static android.os.PersistableBundle defaultMetadata()\nprivate java.lang.String timestampToString()\nprivate void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\npublic android.service.voice.HotwordAudioStream.Builder buildUpon()\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index e22bbd8..dee560b 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -31,6 +31,8 @@
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -99,14 +101,30 @@
private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
/**
- * The bundle key for proximity value
+ * The bundle key for proximity
*
* TODO(b/238896013): Move the proximity logic out of bundle to proper API.
- *
- * @hide
*/
- public static final String EXTRA_PROXIMITY_METERS =
- "android.service.voice.extra.PROXIMITY_METERS";
+ private static final String EXTRA_PROXIMITY =
+ "android.service.voice.extra.PROXIMITY";
+
+ /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+ public static final int PROXIMITY_UNKNOWN = -1;
+
+ /** Proximity value that represents that the object is near. */
+ public static final int PROXIMITY_NEAR = 1;
+
+ /** Proximity value that represents that the object is far. */
+ public static final int PROXIMITY_FAR = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"PROXIMITY"}, value = {
+ PROXIMITY_UNKNOWN,
+ PROXIMITY_NEAR,
+ PROXIMITY_FAR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProximityValue {}
/** Confidence level in the trigger outcome. */
@HotwordConfidenceLevelValue
@@ -220,12 +238,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -348,16 +368,16 @@
// Remove the proximity key from the bundle before checking the bundle size. The
// proximity value is added after the privileged module and can avoid the
// maxBundleSize limitation.
- if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
- double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
- mExtras.remove(EXTRA_PROXIMITY_METERS);
+ if (mExtras.containsKey(EXTRA_PROXIMITY)) {
+ int proximityValue = mExtras.getInt(EXTRA_PROXIMITY);
+ mExtras.remove(EXTRA_PROXIMITY);
// Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
// has parcelable size of 4, but the default bundle has parcelable size of 0.
if (mExtras.size() > 0) {
Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
getMaxBundleSize(), "extras");
}
- mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+ mExtras.putInt(EXTRA_PROXIMITY, proximityValue);
} else {
Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
getMaxBundleSize(), "extras");
@@ -372,6 +392,52 @@
return List.copyOf(mAudioStreams);
}
+ /**
+ * Adds proximity level, either near or far, that is mapped for the given distance into
+ * the bundle. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond.
+ * This mapping will be excluded from the max bundle size calculation because this mapping is
+ * included after the result is returned from the hotword detector service. The value will not
+ * be included if the proximity was unknown.
+ *
+ * @hide
+ */
+ public void setProximity(double distance) {
+ int proximityLevel = convertToProximityLevel(distance);
+ if (proximityLevel != PROXIMITY_UNKNOWN) {
+ mExtras.putInt(EXTRA_PROXIMITY, proximityLevel);
+ }
+ }
+
+ /**
+ * Returns proximity level, which can be either of {@link HotwordDetectedResult#PROXIMITY_NEAR}
+ * or {@link HotwordDetectedResult#PROXIMITY_FAR}. If the proximity is unknown, it will
+ * return {@link HotwordDetectedResult#PROXIMITY_UNKNOWN}.
+ */
+ @ProximityValue
+ public int getProximity() {
+ return mExtras.getInt(EXTRA_PROXIMITY, PROXIMITY_UNKNOWN);
+ }
+
+ /**
+ * Mapping of the proximity distance (meters) to proximity values, unknown, near, and far.
+ * Currently, this mapping is handled by HotwordDetectedResult because it handles just
+ * HotwordDetectionConnection which we know the mapping of. However, the mapping will need to
+ * move to a more centralized place once there are more clients.
+ *
+ * TODO(b/258531144): Move the proximity mapping to a central location
+ */
+ @ProximityValue
+ private int convertToProximityLevel(double distance) {
+ if (distance < 0) {
+ return PROXIMITY_UNKNOWN;
+ } else if (distance <= 3) {
+ return PROXIMITY_NEAR;
+ } else {
+ return PROXIMITY_FAR;
+ }
+ }
+
@DataClass.Suppress("addAudioStreams")
abstract static class BaseBuilder {
/**
@@ -388,6 +454,25 @@
}
}
+ /**
+ * Provides an instance of {@link Builder} with state corresponding to this instance.
+ * @hide
+ */
+ public Builder buildUpon() {
+ return new Builder()
+ .setConfidenceLevel(mConfidenceLevel)
+ .setMediaSyncEvent(mMediaSyncEvent)
+ .setHotwordOffsetMillis(mHotwordOffsetMillis)
+ .setHotwordDurationMillis(mHotwordDurationMillis)
+ .setAudioChannel(mAudioChannel)
+ .setHotwordDetectionPersonalized(mHotwordDetectionPersonalized)
+ .setScore(mScore)
+ .setPersonalizedScore(mPersonalizedScore)
+ .setHotwordPhraseId(mHotwordPhraseId)
+ .setAudioStreams(mAudioStreams)
+ .setExtras(mExtras);
+ }
+
// Code below generated by codegen v1.0.23.
@@ -413,7 +498,7 @@
CONFIDENCE_LEVEL_HIGH,
CONFIDENCE_LEVEL_VERY_HIGH
})
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
public @interface ConfidenceLevel {}
@@ -444,7 +529,7 @@
LIMIT_HOTWORD_OFFSET_MAX_VALUE,
LIMIT_AUDIO_CHANNEL_MAX_VALUE
})
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
/* package-private */ @interface Limit {}
@@ -460,6 +545,30 @@
}
}
+ /** @hide */
+ @IntDef(prefix = "PROXIMITY_", value = {
+ PROXIMITY_UNKNOWN,
+ PROXIMITY_NEAR,
+ PROXIMITY_FAR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Proximity {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String proximityToString(@Proximity int value) {
+ switch (value) {
+ case PROXIMITY_UNKNOWN:
+ return "PROXIMITY_UNKNOWN";
+ case PROXIMITY_NEAR:
+ return "PROXIMITY_NEAR";
+ case PROXIMITY_FAR:
+ return "PROXIMITY_FAR";
+ default: return Integer.toHexString(value);
+ }
+ }
+
@DataClass.Generated.Member
/* package-private */ HotwordDetectedResult(
@HotwordConfidenceLevelValue int confidenceLevel,
@@ -586,12 +695,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -904,12 +1015,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -984,10 +1097,10 @@
}
@DataClass.Generated(
- time = 1665995595979L,
+ time = 1668385264834L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
- inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\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.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\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.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index df69cc0..552a793 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -25,6 +25,7 @@
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Service;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -90,10 +91,10 @@
/**
* Feature flag for Attention Service.
*
- * TODO(b/247920386): Add TestApi annotation
* @hide
*/
- public static final boolean ENABLE_PROXIMITY_RESULT = false;
+ @TestApi
+ public static final boolean ENABLE_PROXIMITY_RESULT = true;
/**
* Indicates that the updated status is successful.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 37fc9f2..007478a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -31,6 +31,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.FloatRange;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -575,6 +576,7 @@
*/
public void reportEngineShown(boolean waitForEngineShown) {
if (mIWallpaperEngine.mShownReported) return;
+ Trace.beginSection("WPMS.reportEngineShown-" + waitForEngineShown);
Log.d(TAG, "reportEngineShown: shouldWait=" + waitForEngineShown);
if (!waitForEngineShown) {
Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
@@ -587,6 +589,7 @@
mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5));
}
}
+ Trace.endSection();
}
/**
@@ -655,6 +658,7 @@
* Called once to initialize the engine. After returning, the
* engine's surface will be created by the framework.
*/
+ @MainThread
public void onCreate(SurfaceHolder surfaceHolder) {
}
@@ -663,6 +667,7 @@
* surface will be destroyed and this Engine object is no longer
* valid.
*/
+ @MainThread
public void onDestroy() {
}
@@ -671,6 +676,7 @@
* hidden. <em>It is very important that a wallpaper only use
* CPU while it is visible.</em>.
*/
+ @MainThread
public void onVisibilityChanged(boolean visible) {
}
@@ -681,6 +687,7 @@
*
* @param insets Insets to apply.
*/
+ @MainThread
public void onApplyWindowInsets(WindowInsets insets) {
}
@@ -691,6 +698,7 @@
* user is interacting with, so if it is slow you will get fewer
* move events.
*/
+ @MainThread
public void onTouchEvent(MotionEvent event) {
}
@@ -700,6 +708,7 @@
* call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
* WallpaperManager.setWallpaperOffsets()}.
*/
+ @MainThread
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
@@ -722,6 +731,7 @@
* @return If returning a result, create a Bundle and place the
* result data in to it. Otherwise return null.
*/
+ @MainThread
public Bundle onCommand(String action, int x, int y, int z,
Bundle extras, boolean resultRequested) {
return null;
@@ -740,6 +750,7 @@
* @hide
*/
@SystemApi
+ @MainThread
public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
}
@@ -747,6 +758,7 @@
* Called when an application has changed the desired virtual size of
* the wallpaper.
*/
+ @MainThread
public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
}
@@ -754,6 +766,7 @@
* Convenience for {@link SurfaceHolder.Callback#surfaceChanged
* SurfaceHolder.Callback.surfaceChanged()}.
*/
+ @MainThread
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@@ -761,6 +774,7 @@
* Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
* SurfaceHolder.Callback.surfaceRedrawNeeded()}.
*/
+ @MainThread
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
}
@@ -768,6 +782,7 @@
* Convenience for {@link SurfaceHolder.Callback#surfaceCreated
* SurfaceHolder.Callback.surfaceCreated()}.
*/
+ @MainThread
public void onSurfaceCreated(SurfaceHolder holder) {
}
@@ -775,6 +790,7 @@
* Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
* SurfaceHolder.Callback.surfaceDestroyed()}.
*/
+ @MainThread
public void onSurfaceDestroyed(SurfaceHolder holder) {
}
@@ -785,6 +801,7 @@
* @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully
* zoomed out.
*/
+ @MainThread
public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) {
}
@@ -834,6 +851,7 @@
*
* @return Wallpaper colors.
*/
+ @MainThread
public @Nullable WallpaperColors onComputeColors() {
return null;
}
@@ -1259,7 +1277,9 @@
didSurface = true;
if (DEBUG) Log.v(TAG, "onSurfaceCreated("
+ mSurfaceHolder + "): " + this);
+ Trace.beginSection("WPMS.Engine.onSurfaceCreated");
onSurfaceCreated(mSurfaceHolder);
+ Trace.endSection();
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
@@ -1285,8 +1305,10 @@
+ ", " + mCurWidth + ", " + mCurHeight
+ "): " + this);
didSurface = true;
+ Trace.beginSection("WPMS.Engine.onSurfaceChanged");
onSurfaceChanged(mSurfaceHolder, mFormat,
mCurWidth, mCurHeight);
+ Trace.endSection();
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
@@ -1303,11 +1325,15 @@
if (DEBUG) {
Log.v(TAG, "dispatching insets=" + windowInsets);
}
+ Trace.beginSection("WPMS.Engine.onApplyWindowInsets");
onApplyWindowInsets(windowInsets);
+ Trace.endSection();
}
if (redrawNeeded) {
+ Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded");
onSurfaceRedrawNeeded(mSurfaceHolder);
+ Trace.endSection();
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
@@ -1332,11 +1358,15 @@
// the state to get them to notice.
if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: "
+ this);
+ Trace.beginSection("WPMS.Engine.onVisibilityChanged-true");
onVisibilityChanged(true);
+ Trace.endSection();
}
if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: "
+ this);
+ Trace.beginSection("WPMS.Engine.onVisibilityChanged-false");
onVisibilityChanged(false);
+ Trace.endSection();
}
} finally {
mIsCreating = false;
@@ -1421,12 +1451,16 @@
mDisplayState = mDisplay.getState();
if (DEBUG) Log.v(TAG, "onCreate(): " + this);
+ Trace.beginSection("WPMS.Engine.onCreate");
onCreate(mSurfaceHolder);
+ Trace.endSection();
mInitializing = false;
mReportedVisible = false;
+ Trace.beginSection("WPMS.Engine.updateSurface");
updateSurface(false, false, false);
+ Trace.endSection();
}
/**
@@ -2236,14 +2270,15 @@
public void reportShown() {
if (!mShownReported) {
mShownReported = true;
+ Trace.beginSection("WPMS.mConnection.engineShown");
try {
mConnection.engineShown(this);
Log.d(TAG, "Wallpaper has updated the surface:"
+ mWallpaperManager.getWallpaperInfo());
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
- return;
}
+ Trace.endSection();
}
}
@@ -2285,6 +2320,27 @@
return mEngine == null ? null : SurfaceControl.mirrorSurface(mEngine.mSurfaceControl);
}
+ private void doAttachEngine() {
+ Trace.beginSection("WPMS.onCreateEngine");
+ Engine engine = onCreateEngine();
+ Trace.endSection();
+ mEngine = engine;
+ Trace.beginSection("WPMS.mConnection.attachEngine-" + mDisplayId);
+ try {
+ mConnection.attachEngine(this, mDisplayId);
+ } catch (RemoteException e) {
+ engine.detach();
+ Log.w(TAG, "Wallpaper host disappeared", e);
+ return;
+ } finally {
+ Trace.endSection();
+ }
+ mActiveEngines.add(engine);
+ Trace.beginSection("WPMS.engine.attach");
+ engine.attach(this);
+ Trace.endSection();
+ }
+
private void doDetachEngine() {
mActiveEngines.remove(mEngine);
mEngine.detach();
@@ -2310,21 +2366,15 @@
}
switch (message.what) {
case DO_ATTACH: {
- Engine engine = onCreateEngine();
- mEngine = engine;
- try {
- mConnection.attachEngine(this, mDisplayId);
- } catch (RemoteException e) {
- engine.detach();
- Log.w(TAG, "Wallpaper host disappeared", e);
- return;
- }
- mActiveEngines.add(engine);
- engine.attach(this);
+ Trace.beginSection("WPMS.DO_ATTACH");
+ doAttachEngine();
+ Trace.endSection();
return;
}
case DO_DETACH: {
+ Trace.beginSection("WPMS.DO_DETACH");
doDetachEngine();
+ Trace.endSection();
return;
}
case DO_SET_DESIRED_SIZE: {
@@ -2405,7 +2455,9 @@
}
} break;
case MSG_REPORT_SHOWN: {
+ Trace.beginSection("WPMS.MSG_REPORT_SHOWN");
reportShown();
+ Trace.endSection();
} break;
default :
Log.w(TAG, "Unknown message type " + message.what);
@@ -2429,8 +2481,10 @@
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
int displayId, @SetWallpaperFlags int which) {
+ Trace.beginSection("WPMS.ServiceWrapper.attach");
mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight, padding, displayId);
+ Trace.endSection();
}
@Override
@@ -2441,16 +2495,20 @@
@Override
public void onCreate() {
+ Trace.beginSection("WPMS.onCreate");
super.onCreate();
+ Trace.endSection();
}
@Override
public void onDestroy() {
+ Trace.beginSection("WPMS.onDestroy");
super.onDestroy();
for (int i=0; i<mActiveEngines.size(); i++) {
mActiveEngines.get(i).detach();
}
mActiveEngines.clear();
+ Trace.endSection();
}
/**
@@ -2468,6 +2526,7 @@
* when the wallpaper is currently set as the active wallpaper and the user
* is in the wallpaper picker viewing a preview of it as well.
*/
+ @MainThread
public abstract Engine onCreateEngine();
@Override
diff --git a/core/java/android/text/style/AccessibilityURLSpan.java b/core/java/android/text/style/AccessibilityURLSpan.java
index bd81623..e280bdf 100644
--- a/core/java/android/text/style/AccessibilityURLSpan.java
+++ b/core/java/android/text/style/AccessibilityURLSpan.java
@@ -26,6 +26,7 @@
* It is used to replace URLSpans in {@link AccessibilityNodeInfo#setText(CharSequence)}
* @hide
*/
+@SuppressWarnings("ParcelableCreator")
public class AccessibilityURLSpan extends URLSpan implements Parcelable {
final AccessibilityClickableSpan mAccessibilityClickableSpan;
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index 85b7ae9..d61228b 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -149,7 +149,7 @@
}
mTextFontWeight = a.getInt(com.android.internal.R.styleable
- .TextAppearance_textFontWeight, -1);
+ .TextAppearance_textFontWeight, /*defValue*/ FontStyle.FONT_WEIGHT_UNSPECIFIED);
final String localeString = a.getString(com.android.internal.R.styleable
.TextAppearance_textLocale);
@@ -215,7 +215,7 @@
mTextColorLink = linkColor;
mTypeface = null;
- mTextFontWeight = -1;
+ mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
mTextLocales = null;
mShadowRadius = 0.0f;
@@ -359,8 +359,8 @@
}
/**
- * Returns the text font weight specified by this span, or <code>-1</code>
- * if it does not specify one.
+ * Returns the text font weight specified by this span, or
+ * <code>FontStyle.FONT_WEIGHT_UNSPECIFIED</code> if it does not specify one.
*/
public int getTextFontWeight() {
return mTextFontWeight;
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
index 18783f5..f6d7c61 100644
--- a/core/java/android/transparency/BinaryTransparencyManager.java
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -24,7 +24,7 @@
import com.android.internal.os.IBinaryTransparencyService;
-import java.util.Map;
+import java.util.List;
/**
* BinaryTransparencyManager defines a number of system interfaces that other system apps or
@@ -66,12 +66,15 @@
}
/**
- * Returns a map of all installed APEXs consisting of package name to SHA256 hash of the
- * package.
- * @return A Map with the following entries: {apex package name : sha256 digest of package}
+ * Gets binary measurements of all installed APEXs, each packed in a Bundle.
+ * @return A List of {@link android.os.Bundle}s with the following keys:
+ * {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO}
+ * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM}
+ * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST}
*/
+ // TODO(b/259422958): Fix static constants referenced here - should be defined here
@NonNull
- public Map getApexInfo() {
+ public List getApexInfo() {
try {
Slog.d(TAG, "Calling backend's getApexInfo()");
return mService.getApexInfo();
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index 1345ddf..d1b9d9f 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -40,5 +40,5 @@
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
-};
+}
diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java
index 2b824bf..72c34d8b 100644
--- a/core/java/android/util/AndroidRuntimeException.java
+++ b/core/java/android/util/AndroidRuntimeException.java
@@ -34,5 +34,4 @@
public AndroidRuntimeException(Exception cause) {
super(cause);
}
-};
-
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 7b6a6d2..4afd268 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -132,6 +132,12 @@
*/
public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
+ /**
+ * Flag to enable/disable biometrics enrollment v2
+ * @hide
+ */
+ public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
+
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -167,6 +173,7 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+ DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index d4cf6e6..3772006 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,6 +1,6 @@
per-file Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
per-file FeatureFlagUtils.java = sbasi@google.com
-per-file FeatureFlagUtils.java = tmfang@google.com
+per-file FeatureFlagUtils.java = edgarwang@google.com
per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS
per-file TypedValue.java = file:/core/java/android/content/res/OWNERS
diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java
index 9fd0ab9..41c171a 100644
--- a/core/java/android/util/Range.java
+++ b/core/java/android/util/Range.java
@@ -356,4 +356,4 @@
private final T mLower;
private final T mUpper;
-};
+}
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 19de396..44318bb 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -696,5 +696,5 @@
sb.append("}");
return sb.toString();
}
-};
+}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 510bde1..44168ca 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -21,6 +21,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import android.accessibilityservice.AccessibilityService;
import android.annotation.NonNull;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -46,11 +47,13 @@
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityRequestPreparer;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -588,6 +591,43 @@
}
}
+ /**
+ * Take a screenshot using {@link ScreenCapture} of this {@link ViewRootImpl}'s {@link
+ * SurfaceControl}.
+ */
+ public void takeScreenshotOfWindowClientThread(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {
+ Message message = PooledLambda.obtainMessage(
+ AccessibilityInteractionController::takeScreenshotOfWindowUiThread,
+ this, interactionId, listener, callback);
+
+ // Screenshot results are returned to the service asynchronously, so the same-thread
+ // message wait logic from #scheduleMessage() is not needed.
+ mHandler.sendMessage(message);
+ }
+
+ private void takeScreenshotOfWindowUiThread(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {
+ try {
+ if ((mViewRootImpl.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_SECURE_WINDOW, interactionId);
+ return;
+ }
+ final ScreenCapture.LayerCaptureArgs captureArgs =
+ new ScreenCapture.LayerCaptureArgs.Builder(mViewRootImpl.getSurfaceControl())
+ .setChildrenOnly(false).setUid(Process.myUid()).build();
+ if (ScreenCapture.captureLayers(captureArgs, listener) != 0) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+ }
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+
public void findFocusClientThread(long accessibilityNodeId, int focusType,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java
index f8aa934..3fc3b6a 100644
--- a/core/java/android/view/CutoutSpecification.java
+++ b/core/java/android/view/CutoutSpecification.java
@@ -394,7 +394,6 @@
Log.e(TAG, "According to SVG definition, it shouldn't happen");
return;
}
- spec.trim();
translateMatrix();
final Path newPath = PathParser.createPathFromPathData(spec);
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 0769f12..91270d4 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.inputmethod.ImeTracker;
/**
* Singular controller of insets to use when there isn't another obvious controller available.
@@ -48,10 +49,10 @@
/**
* @see IWindow#showInsets
*/
- void showInsets(int types, boolean fromIme);
+ void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
/**
* @see IWindow#hideInsets
*/
- void hideInsets(int types, boolean fromIme);
+ void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index a856474..8e16f24 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -29,6 +29,7 @@
import android.view.IScrollCaptureResponseListener;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -68,16 +69,18 @@
*
* @param types internal insets types (WindowInsets.Type.InsetsType) to show
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- void showInsets(int types, boolean fromIme);
+ void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
/**
* Called when a set of insets source window should be hidden by policy.
*
* @param types internal insets types (WindowInsets.Type.InsetsType) to hide
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
- void hideInsets(int types, boolean fromIme);
+ void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 332e97c..02e0fcc 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -65,7 +65,7 @@
public void onWindowFocusGained(boolean hasViewFocus) {
super.onWindowFocusGained(hasViewFocus);
getImm().registerImeConsumer(this);
- if (isRequestedVisible() && getControl() == null) {
+ if ((mController.getRequestedVisibleTypes() & getType()) != 0 && getControl() == null) {
mIsRequestedVisibleAwaitingControl = true;
}
}
@@ -125,7 +125,7 @@
// If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
// this code here means that we now got control, so we can start the animation immediately.
// If client window is trying to control IME and IME is already visible, it is immediate.
- if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) {
+ if (fromIme || (mState.getSource(getInternalType()).isVisible() && getControl() != null)) {
return ShowResult.SHOW_IMMEDIATELY;
}
@@ -169,7 +169,7 @@
@Override
protected boolean isRequestedVisibleAwaitingControl() {
- return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
+ return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingControl;
}
@Override
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 805727c..e775969 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -58,6 +58,7 @@
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
@@ -68,7 +69,7 @@
* Implements {@link WindowInsetsAnimationController}
* @hide
*/
-@VisibleForTesting
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class InsetsAnimationControlImpl implements InternalInsetsAnimationController,
InsetsAnimationControlRunner {
@@ -96,6 +97,8 @@
/** @see WindowInsetsAnimationController#hasZeroInsetsIme */
private final boolean mHasZeroInsetsIme;
private final CompatibilityInfo.Translator mTranslator;
+ @Nullable
+ private final ImeTracker.Token mStatsToken;
private Insets mCurrentInsets;
private Insets mPendingInsets;
private float mPendingFraction;
@@ -114,7 +117,7 @@
@InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
Interpolator interpolator, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- CompatibilityInfo.Translator translator) {
+ CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
mControls = controls;
mListener = listener;
mTypes = types;
@@ -128,7 +131,7 @@
null /* typeSideMap */);
mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
typeSideMap);
- mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+ mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
if (mHasZeroInsetsIme) {
// IME has shownInsets of ZERO, and can't map to a side by default.
// Map zero insets IME to bottom, making it a special case of bottom insets.
@@ -141,7 +144,7 @@
mCurrentInsets = calculateInsets(mInitialInsetsState, controls, true /* shown */);
mHiddenInsets = calculateInsets(null, controls, false /* shown */);
mShownInsets = calculateInsets(null, controls, true /* shown */);
- mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+ mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
buildSideControlsMap(mSideControlsMap, controls);
}
mPendingInsets = mCurrentInsets;
@@ -152,6 +155,7 @@
mAnimationType = animationType;
mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
mTranslator = translator;
+ mStatsToken = statsToken;
mController.startAnimation(this, listener, types, mAnimation,
new Bounds(mHiddenInsets, mShownInsets));
}
@@ -228,6 +232,11 @@
}
@Override
+ public ImeTracker.Token getStatsToken() {
+ return mStatsToken;
+ }
+
+ @Override
public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */);
}
@@ -253,10 +262,10 @@
}
}
- @VisibleForTesting
/**
* @return Whether the finish callback of this animation should be invoked.
*/
+ @VisibleForTesting
public boolean applyChangeInsets(@Nullable InsetsState outState) {
if (mCancelled) {
if (DEBUG) Log.d(TAG, "applyChangeInsets canceled");
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 1cb00e3..cf40e7e 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -16,11 +16,12 @@
package android.view;
+import android.annotation.Nullable;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsController.AnimationType;
-import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
/**
* Interface representing a runner for an insets animation.
@@ -63,10 +64,10 @@
WindowInsetsAnimation getAnimation();
/**
- * @return Whether {@link #getTypes()} maps to a specific {@link InternalInsetsType}.
+ * @return Whether {@link #getTypes()} contains a specific {@link InsetsType}.
*/
- default boolean controlsInternalType(@InternalInsetsType int type) {
- return InsetsState.toInternalType(getTypes()).contains(type);
+ default boolean controlsType(@InsetsType int type) {
+ return (getTypes() & type) != 0;
}
/**
@@ -75,6 +76,12 @@
@AnimationType int getAnimationType();
/**
+ * @return The token tracking the current IME request or {@code null} otherwise.
+ */
+ @Nullable
+ ImeTracker.Token getStatsToken();
+
+ /**
*
* Export the state of classes that implement this interface into a protocol buffer
* output stream.
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index fc97541..f7b9aa2 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -34,6 +34,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
/**
* Insets animation runner that uses {@link InsetsAnimationThread} to run the animation off from the
@@ -112,12 +113,13 @@
@InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
Interpolator interpolator, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- CompatibilityInfo.Translator translator, Handler mainThreadHandler) {
+ CompatibilityInfo.Translator translator, Handler mainThreadHandler,
+ @Nullable ImeTracker.Token statsToken) {
mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller;
mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
- translator);
+ translator, statsToken);
InsetsAnimationThread.getHandler().post(() -> {
if (mControl.isCancelled()) {
return;
@@ -141,6 +143,11 @@
}
@Override
+ public ImeTracker.Token getStatsToken() {
+ return mControl.getStatsToken();
+ }
+
+ @Override
@UiThread
public int getTypes() {
return mControl.getTypes();
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8b38e9e..fbd8226 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -24,6 +24,8 @@
import static android.view.InsetsState.toInternalType;
import static android.view.InsetsState.toPublicType;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.FIRST;
+import static android.view.WindowInsets.Type.LAST;
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.ime;
@@ -42,6 +44,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Trace;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -58,6 +61,7 @@
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -744,8 +748,8 @@
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
InsetsSource source = newState.peekSource(type);
if (source == null) continue;
- @AnimationType int animationType = getAnimationType(type);
@InsetsType int insetsType = toPublicType(type);
+ @AnimationType int animationType = getAnimationType(insetsType);
if (!source.isUserControllable()) {
// The user animation is not allowed when visible frame is empty.
disabledUserAnimationTypes |= insetsType;
@@ -788,8 +792,7 @@
if (diff != 0) {
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
- if (consumer.getControl() != null
- && (toPublicType(consumer.getType()) & diff) != 0) {
+ if (consumer.getControl() != null && (consumer.getType() & diff) != 0) {
mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
mHandler.post(mInvokeControllableInsetsChangedListeners);
break;
@@ -897,7 +900,7 @@
// Ensure to update all existing source consumers
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
- final InsetsSourceControl control = mTmpControlArray.get(consumer.getType());
+ final InsetsSourceControl control = mTmpControlArray.get(consumer.getInternalType());
// control may be null, but we still need to update the control to null if it got
// revoked.
@@ -928,10 +931,12 @@
hideTypes[0] &= ~animatingTypes;
if (showTypes[0] != 0) {
- applyAnimation(showTypes[0], true /* show */, false /* fromIme */);
+ applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
+ null /* statsToken */);
}
if (hideTypes[0] != 0) {
- applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
+ applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
+ null /* statsToken */);
}
if (mControllableTypes != controllableTypes) {
@@ -947,11 +952,12 @@
@Override
public void show(@InsetsType int types) {
- show(types, false /* fromIme */);
+ show(types, false /* fromIme */, null /* statsToken */);
}
- @VisibleForTesting
- public void show(@InsetsType int types, boolean fromIme) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void show(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & ime()) != 0) {
Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")");
}
@@ -978,44 +984,57 @@
true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
pendingRequest.animationType,
pendingRequest.layoutInsetsDuringAnimation,
- pendingRequest.useInsetsAnimationThread);
+ pendingRequest.useInsetsAnimationThread, statsToken);
return;
}
// TODO: Support a ResultReceiver for IME.
// TODO(b/123718661): Make show() work for multi-session IME.
int typesReady = 0;
- final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
- for (int i = internalTypes.size() - 1; i >= 0; i--) {
- @InternalInsetsType int internalType = internalTypes.valueAt(i);
- @AnimationType int animationType = getAnimationType(internalType);
- InsetsSourceConsumer consumer = getSourceConsumer(internalType);
- if (consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
+ for (int type = FIRST; type <= LAST; type = type << 1) {
+ if ((types & type) == 0) {
+ continue;
+ }
+ @AnimationType final int animationType = getAnimationType(type);
+ final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+ final boolean isImeAnimation = type == ime();
+ if (requestedVisible && animationType == ANIMATION_TYPE_NONE
|| animationType == ANIMATION_TYPE_SHOW) {
// no-op: already shown or animating in (because window visibility is
// applied before starting animation).
if (DEBUG) Log.d(TAG, String.format(
"show ignored for type: %d animType: %d requestedVisible: %s",
- consumer.getType(), animationType, consumer.isRequestedVisible()));
+ type, animationType, requestedVisible));
+ if (isImeAnimation) {
+ ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
continue;
}
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
+ if (isImeAnimation) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
continue;
}
- typesReady |= InsetsState.toPublicType(consumer.getType());
+ if (isImeAnimation) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
+ typesReady |= type;
}
if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
- applyAnimation(typesReady, true /* show */, fromIme);
+ applyAnimation(typesReady, true /* show */, fromIme, statsToken);
}
@Override
public void hide(@InsetsType int types) {
- hide(types, false /* fromIme */);
+ hide(types, false /* fromIme */, null /* statsToken */);
}
@VisibleForTesting
- public void hide(@InsetsType int types, boolean fromIme) {
+ public void hide(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
mHost.getInputMethodManager(), null /* icProto */);
@@ -1024,19 +1043,29 @@
Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
}
int typesReady = 0;
- final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
- for (int i = internalTypes.size() - 1; i >= 0; i--) {
- @InternalInsetsType int internalType = internalTypes.valueAt(i);
- @AnimationType int animationType = getAnimationType(internalType);
- InsetsSourceConsumer consumer = getSourceConsumer(internalType);
- if (!consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
- || animationType == ANIMATION_TYPE_HIDE) {
- // no-op: already hidden or animating out.
+ for (int type = FIRST; type <= LAST; type = type << 1) {
+ if ((types & type) == 0) {
continue;
}
- typesReady |= InsetsState.toPublicType(consumer.getType());
+ @AnimationType final int animationType = getAnimationType(type);
+ final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+ final boolean isImeAnimation = type == ime();
+ if (!requestedVisible && animationType == ANIMATION_TYPE_NONE
+ || animationType == ANIMATION_TYPE_HIDE) {
+ // no-op: already hidden or animating out (because window visibility is
+ // applied before starting animation).
+ if (isImeAnimation) {
+ ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
+ continue;
+ }
+ if (isImeAnimation) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
+ typesReady |= type;
}
- applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
+ applyAnimation(typesReady, false /* show */, fromIme, statsToken);
}
@Override
@@ -1065,7 +1094,7 @@
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
- false /* useInsetsAnimationThread */);
+ false /* useInsetsAnimationThread */, null /* statsToken */);
}
private void controlAnimationUnchecked(@InsetsType int types,
@@ -1074,7 +1103,8 @@
long durationMs, Interpolator interpolator,
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- boolean useInsetsAnimationThread) {
+ boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if ((types & mTypesBeingCancelled) != 0) {
throw new IllegalStateException("Cannot start a new insets animation of "
+ Type.toString(types)
@@ -1149,14 +1179,16 @@
? new InsetsAnimationThreadControlRunner(controls,
frame, mState, listener, typesReady, this, durationMs, interpolator,
animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
- mHost.getHandler())
+ mHost.getHandler(), statsToken)
: new InsetsAnimationControlImpl(controls,
frame, mState, listener, typesReady, this, durationMs, interpolator,
- animationType, layoutInsetsDuringAnimation, mHost.getTranslator());
+ animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
+ statsToken);
if ((typesReady & WindowInsets.Type.ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
mHost.getInputMethodManager(), null /* icProto */);
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
mRunningAnimations.add(new RunningAnimation(runner, animationType));
if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
+ useInsetsAnimationThread);
@@ -1228,13 +1260,13 @@
if (!canRun) {
if (WARN) Log.w(TAG, String.format(
"collectSourceControls can't continue show for type: %s fromIme: %b",
- InsetsState.typeToString(consumer.getType()), fromIme));
+ InsetsState.typeToString(consumer.getInternalType()), fromIme));
continue;
}
final InsetsSourceControl control = consumer.getControl();
if (control != null && control.getLeash() != null) {
- controls.put(consumer.getType(), new InsetsSourceControl(control));
- typesReady |= toPublicType(consumer.getType());
+ controls.put(control.getType(), new InsetsSourceControl(control));
+ typesReady |= consumer.getType();
} else if (animationType == ANIMATION_TYPE_SHOW) {
if (DEBUG) Log.d(TAG, "collectSourceControls no control for show(). fromIme: "
+ fromIme);
@@ -1260,25 +1292,15 @@
private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
@InsetsType int types) {
-
- final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-
// Generally, we want to layout the opposite of the current state. This is to make animation
// callbacks easy to use: The can capture the layout values and then treat that as end-state
// during the animation.
//
// However, if controlling multiple sources, we want to treat it as shown if any of the
// types is currently hidden.
- for (int i = internalTypes.size() - 1; i >= 0; i--) {
- InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
- if (consumer == null) {
- continue;
- }
- if (!consumer.isRequestedVisible()) {
- return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
- }
- }
- return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+ return (mRequestedVisibleTypes & types) != types
+ ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+ : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
}
private void cancelExistingControllers(@InsetsType int types) {
@@ -1318,11 +1340,18 @@
// requested visibility.
return;
}
+ final ImeTracker.Token statsToken = runner.getStatsToken();
if (shown) {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
showDirectly(runner.getTypes(), true /* fromIme */);
+ ImeTracker.get().onShown(statsToken);
} else {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
hideDirectly(runner.getTypes(), true /* animationFinished */,
runner.getAnimationType(), true /* fromIme */);
+ ImeTracker.get().onHidden(statsToken);
}
}
@@ -1332,24 +1361,33 @@
}
void notifyControlRevoked(InsetsSourceConsumer consumer) {
- final @InsetsType int types = toPublicType(consumer.getType());
+ final @InsetsType int type = consumer.getType();
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
- control.notifyControlRevoked(types);
+ control.notifyControlRevoked(type);
if (control.getControllingTypes() == 0) {
cancelAnimation(control, true /* invokeCallback */);
}
}
- if (consumer.getType() == ITYPE_IME) {
+ if (type == ime()) {
abortPendingImeControlRequest();
}
}
private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
- if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d, host: %s",
- control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
if (invokeCallback) {
+ ImeTracker.get().onCancelled(control.getStatsToken(),
+ ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
control.cancel();
+ } else {
+ // Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
+ ImeTracker.get().onProgress(control.getStatsToken(),
+ ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+ }
+ if (DEBUG) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "cancelAnimation of types: %d, animType: %d, host: %s",
+ control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
}
boolean stateChanged = false;
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
@@ -1425,27 +1463,25 @@
}
@VisibleForTesting
- public @AnimationType int getAnimationType(@InternalInsetsType int type) {
+ public @AnimationType int getAnimationType(@InsetsType int type) {
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
- if (control.controlsInternalType(type)) {
+ if (control.controlsType(type)) {
return mRunningAnimations.get(i).type;
}
}
return ANIMATION_TYPE_NONE;
}
- @VisibleForTesting
- public void onRequestedVisibilityChanged(InsetsSourceConsumer consumer) {
- final @InsetsType int type = InsetsState.toPublicType(consumer.getType());
- final int requestedVisibleTypes = consumer.isRequestedVisible()
- ? mRequestedVisibleTypes | type
- : mRequestedVisibleTypes & ~type;
+ void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+ final @InsetsType int requestedVisibleTypes =
+ (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
if (mRequestedVisibleTypes != requestedVisibleTypes) {
- mRequestedVisibleTypes = requestedVisibleTypes;
- if (WindowInsets.Type.hasCompatSystemBars(type)) {
+ if (WindowInsets.Type.hasCompatSystemBars(
+ mRequestedVisibleTypes ^ requestedVisibleTypes)) {
mCompatSysUiVisibilityStaled = true;
}
+ mRequestedVisibleTypes = requestedVisibleTypes;
}
}
@@ -1461,7 +1497,8 @@
}
@VisibleForTesting
- public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme) {
+ public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
// TODO(b/166736352): We should only skip the animation of specific types, not all types.
boolean skipAnim = false;
if ((types & ime()) != 0) {
@@ -1474,12 +1511,12 @@
&& consumer.hasViewFocusWhenWindowFocusGain();
}
}
- applyAnimation(types, show, fromIme, skipAnim);
+ applyAnimation(types, show, fromIme, skipAnim, statsToken);
}
@VisibleForTesting
public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
- boolean skipAnim) {
+ boolean skipAnim, @Nullable ImeTracker.Token statsToken) {
if (types == 0) {
// nothing to animate.
if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate");
@@ -1499,12 +1536,11 @@
listener.getDurationMs(), listener.getInsetsInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
- !hasAnimationCallbacks /* useInsetsAnimationThread */);
+ !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
}
- private void hideDirectly(
- @InsetsType int types, boolean animationFinished, @AnimationType int animationType,
- boolean fromIme) {
+ private void hideDirectly(@InsetsType int types, boolean animationFinished,
+ @AnimationType int animationType, boolean fromIme) {
if ((types & ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly",
mHost.getInputMethodManager(), null /* icProto */);
@@ -1664,9 +1700,9 @@
@InsetsType int result = 0;
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
- InsetsSource source = mState.peekSource(consumer.mType);
+ InsetsSource source = mState.peekSource(consumer.getInternalType());
if (consumer.getControl() != null && source != null && source.isUserControllable()) {
- result |= toPublicType(consumer.mType);
+ result |= consumer.getType();
}
}
return result & ~mState.calculateUncontrollableInsetsFromFrame(mFrame);
@@ -1707,12 +1743,11 @@
}
@Override
- public void reportPerceptible(int types, boolean perceptible) {
- final ArraySet<Integer> internalTypes = toInternalType(types);
+ public void reportPerceptible(@InsetsType int types, boolean perceptible) {
final int size = mSourceConsumers.size();
for (int i = 0; i < size; i++) {
final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
- if (internalTypes.contains(consumer.getType())) {
+ if ((consumer.getType() & types) != 0) {
consumer.onPerceptible(perceptible);
}
}
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index edcfc95..778c677 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -37,6 +37,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
/**
* Runs a fake animation of resizing insets to produce insets animation callbacks.
@@ -92,6 +93,12 @@
}
@Override
+ public ImeTracker.Token getStatsToken() {
+ // Return null as resizing the IME view is not explicitly tracked.
+ return null;
+ }
+
+ @Override
public void cancel() {
if (mCancelled || mFinished) {
return;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 7a498ad..21c0395 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -27,7 +27,6 @@
import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.getDefaultVisibility;
-import static android.view.InsetsState.toPublicType;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
@@ -74,9 +73,9 @@
}
protected final InsetsController mController;
- protected boolean mRequestedVisible;
protected final InsetsState mState;
- protected final @InternalInsetsType int mType;
+ private final @InternalInsetsType int mInternalType;
+ private final @InsetsType int mType;
private static final String TAG = "InsetsSourceConsumer";
private final Supplier<Transaction> mTransactionSupplier;
@@ -99,11 +98,11 @@
*/
public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
Supplier<Transaction> transactionSupplier, InsetsController controller) {
- mType = type;
+ mType = InsetsState.toPublicType(type);
+ mInternalType = type;
mState = state;
mTransactionSupplier = transactionSupplier;
mController = controller;
- mRequestedVisible = getDefaultVisibility(type);
}
/**
@@ -117,7 +116,7 @@
*/
public boolean setControl(@Nullable InsetsSourceControl control,
@InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
- if (mType == ITYPE_IME) {
+ if (mInternalType == ITYPE_IME) {
ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl",
mController.getHost().getInputMethodManager(), null /* icProto */);
}
@@ -141,9 +140,10 @@
mController.notifyControlRevoked(this);
// Check if we need to restore server visibility.
- final InsetsSource source = mState.getSource(mType);
+ final InsetsSource source = mState.getSource(mInternalType);
final boolean serverVisibility =
- mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType);
+ mController.getLastDispatchedState().getSourceOrDefaultVisibility(
+ mInternalType);
if (source.isVisible() != serverVisibility) {
source.setVisible(serverVisibility);
mController.notifyVisibilityChanged();
@@ -159,9 +159,9 @@
if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b",
mController.getHost().getRootViewTitle(), requestedVisible));
if (requestedVisible) {
- showTypes[0] |= toPublicType(getType());
+ showTypes[0] |= mType;
} else {
- hideTypes[0] |= toPublicType(getType());
+ hideTypes[0] |= mType;
}
} else {
// We are gaining control, but don't need to run an animation.
@@ -172,7 +172,7 @@
// If we have a new leash, make sure visibility is up-to-date, even though we
// didn't want to run an animation above.
- if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
+ if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
applyRequestedVisibilityToControl();
}
@@ -195,29 +195,32 @@
/**
* Determines if the consumer will be shown after control is available.
- * Note: for system bars this method is same as {@link #isRequestedVisible()}.
*
* @return {@code true} if consumer has a pending show.
*/
protected boolean isRequestedVisibleAwaitingControl() {
- return isRequestedVisible();
+ return (mController.getRequestedVisibleTypes() & mType) != 0;
}
- int getType() {
+ @InsetsType int getType() {
return mType;
}
+ @InternalInsetsType int getInternalType() {
+ return mInternalType;
+ }
+
@VisibleForTesting
public void show(boolean fromIme) {
if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
- InsetsState.typeToString(mType), fromIme));
+ InsetsState.typeToString(mInternalType), fromIme));
setRequestedVisible(true);
}
@VisibleForTesting
public void hide() {
if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
- InsetsState.typeToString(mType), mController.getHost().getRootViewTitle()));
+ InsetsState.typeToString(mInternalType), mController.getHost().getRootViewTitle()));
setRequestedVisible(false);
}
@@ -245,11 +248,13 @@
}
boolean applyLocalVisibilityOverride() {
- final InsetsSource source = mState.peekSource(mType);
- final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
+ final InsetsSource source = mState.peekSource(mInternalType);
+ final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(
+ mInternalType);
final boolean hasControl = mSourceControl != null;
+ final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
- if (mType == ITYPE_IME) {
+ if (mInternalType == ITYPE_IME) {
ImeTracing.getInstance().triggerClientDump(
"InsetsSourceConsumer#applyLocalVisibilityOverride",
mController.getHost().getInputMethodManager(), null /* icProto */);
@@ -259,23 +264,18 @@
if (!hasControl) {
if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
+ mController.getHost().getRootViewTitle()
- + " requestedVisible " + mRequestedVisible);
+ + " requestedVisible=" + requestedVisible);
return false;
}
- if (isVisible == mRequestedVisible) {
+ if (isVisible == requestedVisible) {
return false;
}
if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
- mController.getHost().getRootViewTitle(), mRequestedVisible));
- mState.getSource(mType).setVisible(mRequestedVisible);
+ mController.getHost().getRootViewTitle(), requestedVisible));
+ mState.getSource(mInternalType).setVisible(requestedVisible);
return true;
}
- @VisibleForTesting
- public boolean isRequestedVisible() {
- return mRequestedVisible;
- }
-
/**
* Request to show current window type.
*
@@ -314,7 +314,7 @@
@VisibleForTesting(visibility = PACKAGE)
public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
- InsetsSource source = mState.peekSource(mType);
+ InsetsSource source = mState.peekSource(mInternalType);
if (source == null || animationType == ANIMATION_TYPE_NONE
|| source.getFrame().equals(newSource.getFrame())) {
mPendingFrame = null;
@@ -339,7 +339,7 @@
@VisibleForTesting(visibility = PACKAGE)
public boolean notifyAnimationFinished() {
if (mPendingFrame != null) {
- InsetsSource source = mState.getSource(mType);
+ InsetsSource source = mState.getSource(mInternalType);
source.setFrame(mPendingFrame);
source.setVisibleFrame(mPendingVisibleFrame);
mPendingFrame = null;
@@ -354,11 +354,8 @@
* the moment.
*/
protected void setRequestedVisible(boolean requestedVisible) {
- if (mRequestedVisible != requestedVisible) {
- mRequestedVisible = requestedVisible;
- mController.onRequestedVisibilityChanged(this);
- if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
- }
+ mController.setRequestedVisibleTypes(requestedVisible ? mType : 0, mType);
+ if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
if (applyLocalVisibilityOverride()) {
mController.notifyVisibilityChanged();
}
@@ -369,25 +366,26 @@
return;
}
+ final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
try (Transaction t = mTransactionSupplier.get()) {
- if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
- if (mRequestedVisible) {
+ if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
+ if (requestedVisible) {
t.show(mSourceControl.getLeash());
} else {
t.hide(mSourceControl.getLeash());
}
// Ensure the alpha value is aligned with the actual requested visibility.
- t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
+ t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
t.apply();
}
- onPerceptible(mRequestedVisible);
+ onPerceptible(requestedVisible);
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType));
+ proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mInternalType));
proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
- proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible);
+ proto.write(IS_REQUESTED_VISIBLE, (mController.getRequestedVisibleTypes() & mType) != 0);
if (mSourceControl != null) {
mSourceControl.dumpDebug(proto, SOURCE_CONTROL);
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index e91839b..a8cc9b6 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -702,7 +702,7 @@
result.add(ITYPE_NAVIGATION_BAR);
result.add(ITYPE_EXTRA_NAVIGATION_BAR);
}
- if ((types & Type.GENERIC_OVERLAYS) != 0) {
+ if ((types & Type.SYSTEM_OVERLAYS) != 0) {
result.add(ITYPE_LEFT_GENERIC_OVERLAY);
result.add(ITYPE_TOP_GENERIC_OVERLAY);
result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
@@ -752,7 +752,7 @@
case ITYPE_TOP_GENERIC_OVERLAY:
case ITYPE_RIGHT_GENERIC_OVERLAY:
case ITYPE_BOTTOM_GENERIC_OVERLAY:
- return Type.GENERIC_OVERLAYS;
+ return Type.SYSTEM_OVERLAYS;
case ITYPE_CAPTION_BAR:
return Type.CAPTION_BAR;
case ITYPE_IME:
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index ea97995..ff282ba 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.app.WindowConfiguration.ActivityType;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
@@ -157,7 +158,7 @@
}
}
- public static final @android.annotation.NonNull Creator<RemoteAnimationDefinition> CREATOR =
+ public static final @NonNull Creator<RemoteAnimationDefinition> CREATOR =
new Creator<RemoteAnimationDefinition>() {
public RemoteAnimationDefinition createFromParcel(Parcel in) {
return new RemoteAnimationDefinition(in);
@@ -199,18 +200,17 @@
return 0;
}
- private static final @android.annotation.NonNull Creator<RemoteAnimationAdapterEntry> CREATOR
- = new Creator<RemoteAnimationAdapterEntry>() {
+ public static final @NonNull Parcelable.Creator<RemoteAnimationAdapterEntry> CREATOR =
+ new Parcelable.Creator<RemoteAnimationAdapterEntry>() {
+ @Override
+ public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
+ return new RemoteAnimationAdapterEntry(in);
+ }
- @Override
- public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
- return new RemoteAnimationAdapterEntry(in);
- }
-
- @Override
- public RemoteAnimationAdapterEntry[] newArray(int size) {
- return new RemoteAnimationAdapterEntry[size];
- }
- };
+ @Override
+ public RemoteAnimationAdapterEntry[] newArray(int size) {
+ return new RemoteAnimationAdapterEntry[size];
+ }
+ };
}
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 3acb053..1889772 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -159,9 +159,11 @@
}
/**
- * Use {@link SurfaceView#setChildSurfacePackage} or manually fix
- * accessibility (see SurfaceView implementation).
- * @hide
+ * Returns the {@link android.view.SurfaceControl} associated with this SurfacePackage for
+ * cases where more control is required.
+ *
+ * @return the SurfaceControl associated with this SurfacePackage and its containing
+ * SurfaceControlViewHost
*/
public @NonNull SurfaceControl getSurfaceControl() {
return mSurfaceControl;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b24303b..720813a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1073,7 +1073,7 @@
private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
SyncBufferTransactionCallback syncBufferTransactionCallback) {
- getViewRootImpl().addToSync(syncBufferCallback ->
+ getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
redrawNeededAsync(callbacks, () -> {
Transaction t = null;
if (mBlastBufferQueue != null) {
@@ -1081,7 +1081,7 @@
t = syncBufferTransactionCallback.waitForTransaction();
}
- syncBufferCallback.onBufferReady(t);
+ syncBufferCallback.onTransactionReady(t);
onDrawFinished();
}));
}
@@ -1092,9 +1092,9 @@
mSyncGroups.add(syncGroup);
}
- syncGroup.addToSync(syncBufferCallback -> redrawNeededAsync(callbacks,
- () -> {
- syncBufferCallback.onBufferReady(null);
+ syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
+ redrawNeededAsync(callbacks, () -> {
+ syncBufferCallback.onTransactionReady(null);
onDrawFinished();
synchronized (mSyncGroups) {
mSyncGroups.remove(syncGroup);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index d77e882..164a494 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -587,6 +587,13 @@
updateWebViewOverlayCallbacks();
}
+ @Override
+ public void notifyCallbackPending() {
+ if (isEnabled()) {
+ super.notifyCallbackPending();
+ }
+ }
+
/**
* Updates the light position based on the position of the window.
*
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 6d25523..00170cb 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -28,7 +28,7 @@
import java.util.Map;
/**
- * Helper for tracking the velocity of touch events, for implementing
+ * Helper for tracking the velocity of motion events, for implementing
* flinging and other such gestures.
*
* Use {@link #obtain} to retrieve a new instance of the class when you are going
@@ -43,6 +43,15 @@
private static final int ACTIVE_POINTER_ID = -1;
+ /** @hide */
+ @IntDef(value = {
+ MotionEvent.AXIS_X,
+ MotionEvent.AXIS_Y,
+ MotionEvent.AXIS_SCROLL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VelocityTrackableMotionEventAxis {}
+
/**
* Velocity Tracker Strategy: Invalid.
*
@@ -306,10 +315,12 @@
}
/**
- * Checks whether a given motion axis is supported for velocity tracking.
+ * Checks whether a given velocity-trackable {@link MotionEvent} axis is supported for velocity
+ * tracking by this {@link VelocityTracker} instance (refer to
+ * {@link #getAxisVelocity(int, int)} for a list of potentially velocity-trackable axes).
*
- * <p>The axis values that would make sense to use for this method are the ones defined in the
- * {@link MotionEvent} class.
+ * <p>Note that the value returned from this method will stay the same for a given instance, so
+ * a single check for axis support is enough per a {@link VelocityTracker} instance.
*
* @param axis The axis to check for velocity support.
* @return {@code true} if {@code axis} is supported for velocity tracking, or {@code false}
@@ -317,7 +328,7 @@
* @see #getAxisVelocity(int, int)
* @see #getAxisVelocity(int)
*/
- public boolean isAxisSupported(int axis) {
+ public boolean isAxisSupported(@VelocityTrackableMotionEventAxis int axis) {
return nativeIsAxisSupported(axis);
}
@@ -421,13 +432,16 @@
* calling this function.
*
* <p>In addition to {@link MotionEvent#AXIS_X} and {@link MotionEvent#AXIS_Y} which have been
- * supported since the introduction of this class, the following axes are supported for this
+ * supported since the introduction of this class, the following axes can be candidates for this
* method:
* <ul>
* <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
* {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
* </ul>
*
+ * <p>Before accessing velocities of an axis using this method, check that your
+ * {@link VelocityTracker} instance supports the axis by using {@link #isAxisSupported(int)}.
+ *
* @param axis Which axis' velocity to return.
* @param id Which pointer's velocity to return.
* @return The previously computed velocity for {@code axis} for pointer ID of {@code id} if
@@ -435,7 +449,7 @@
* for the axis.
* @see #isAxisSupported(int)
*/
- public float getAxisVelocity(int axis, int id) {
+ public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis, int id) {
return nativeGetVelocity(mPtr, axis, id);
}
@@ -450,7 +464,7 @@
* @see #isAxisSupported(int)
* @see #getAxisVelocity(int, int)
*/
- public float getAxisVelocity(int axis) {
+ public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis) {
return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2b071db..d709840 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8457,6 +8457,18 @@
* accurately supplying the semantics of their UI.
* They should not need to specify what exactly is announced to users.
*
+ * <p>
+ * In general, only announce transitions and don’t generate a confirmation message for simple
+ * actions like a button press. Label your controls concisely and precisely instead, and for
+ * significant UI changes like window changes, use
+ * {@link android.app.Activity#setTitle(CharSequence)} and
+ * {@link View#setAccessibilityPaneTitle(CharSequence)}.
+ *
+ * <p>
+ * Use {@link View#setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+ * views within the user interface. These should still be used sparingly as they may generate
+ * announcements every time a View is updated.
+ *
* @param text The announcement text.
*/
public void announceForAccessibility(CharSequence text) {
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index f51d9ba..58aee61 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -822,6 +822,7 @@
*
* @hide
*/
+ @TestApi
public static long getSendRecurringAccessibilityEventsInterval() {
return SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e664ebf..0f970bf 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -88,6 +88,7 @@
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
import android.animation.AnimationHandler;
import android.animation.LayoutTransition;
import android.annotation.AnyThread;
@@ -192,12 +193,14 @@
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
import android.view.contentcapture.MainContentCaptureSession;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import android.window.ClientWindowFrames;
import android.window.CompatOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import android.window.ScreenCapture;
import android.window.SurfaceSyncGroup;
import android.window.WindowOnBackInvokedDispatcher;
@@ -228,6 +231,7 @@
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -711,7 +715,7 @@
private final InsetsState mTempInsets = new InsetsState();
private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
- private float mInvSizeCompatScale = 1f;
+ private float mInvCompatScale = 1f;
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
= new ViewTreeObserver.InternalInsetsInfo();
@@ -848,7 +852,7 @@
}
private SurfaceSyncGroup mSyncGroup;
- private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
+ private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
private int mNumSyncsInProgress = 0;
private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
@@ -1105,11 +1109,11 @@
private WindowConfiguration getCompatWindowConfiguration() {
final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
- if (mInvSizeCompatScale == 1f) {
+ if (mInvCompatScale == 1f) {
return winConfig;
}
mTempWinConfig.setTo(winConfig);
- mTempWinConfig.scale(mInvSizeCompatScale);
+ mTempWinConfig.scale(mInvCompatScale);
return mTempWinConfig;
}
@@ -1241,11 +1245,11 @@
controlInsetsForCompatibility(mWindowAttributes);
Rect attachedFrame = new Rect();
- final float[] sizeCompatScale = { 1f };
+ final float[] compatScale = { 1f };
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
- mTempControls, attachedFrame, sizeCompatScale);
+ mTempControls, attachedFrame, compatScale);
if (!attachedFrame.isValid()) {
attachedFrame = null;
}
@@ -1255,8 +1259,8 @@
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
mTmpFrames.attachedFrame = attachedFrame;
- mTmpFrames.sizeCompatScale = sizeCompatScale[0];
- mInvSizeCompatScale = 1f / sizeCompatScale[0];
+ mTmpFrames.compatScale = compatScale[0];
+ mInvCompatScale = 1f / compatScale[0];
} catch (RemoteException | RuntimeException e) {
mAdded = false;
mView = null;
@@ -1405,7 +1409,11 @@
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
- AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ if (!mRemoved || !mAppVisible) {
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ } else if (LOCAL_LOGV) {
+ Log.v(mTag, "setView() enabling visibility when removed");
+ }
}
}
}
@@ -1756,7 +1764,12 @@
mAppVisibilityChanged = true;
scheduleTraversals();
}
- AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ // Only enable if the window is not already removed (via earlier call to doDie())
+ if (!mRemoved || !mAppVisible) {
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ } else if (LOCAL_LOGV) {
+ Log.v(mTag, "handleAppVisibility() enabling visibility when removed");
+ }
}
}
@@ -1787,24 +1800,24 @@
mTranslator.translateRectInScreenToAppWindow(displayFrame);
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
- final float sizeCompatScale = frames.sizeCompatScale;
+ final float compatScale = frames.compatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
final boolean attachedFrameChanged = LOCAL_LAYOUT
&& !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean resizeModeChanged = mResizeMode != resizeMode;
- final boolean sizeCompatScaleChanged = mTmpFrames.sizeCompatScale != sizeCompatScale;
+ final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
&& !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
- && !sizeCompatScaleChanged) {
+ && !compatScaleChanged) {
return;
}
mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
mResizeMode = resizeMode;
- mTmpFrames.sizeCompatScale = sizeCompatScale;
- mInvSizeCompatScale = 1f / sizeCompatScale;
+ mTmpFrames.compatScale = compatScale;
+ mInvCompatScale = 1f / compatScale;
if (configChanged) {
// If configuration changed - notify about that and, maybe, about move to display.
@@ -2305,6 +2318,7 @@
*/
void notifyRendererOfFramePending() {
if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.notifyCallbackPending();
mAttachInfo.mThreadedRenderer.notifyFramePending();
}
}
@@ -3607,8 +3621,8 @@
mPendingTransitions.clear();
}
- if (mSyncBufferCallback != null) {
- mSyncBufferCallback.onBufferReady(null);
+ if (mTransactionReadyCallback != null) {
+ mTransactionReadyCallback.onTransactionReady(null);
}
} else if (cancelAndRedraw) {
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3623,8 +3637,8 @@
}
mPendingTransitions.clear();
}
- if (!performDraw() && mSyncBufferCallback != null) {
- mSyncBufferCallback.onBufferReady(null);
+ if (!performDraw() && mTransactionReadyCallback != null) {
+ mTransactionReadyCallback.onTransactionReady(null);
}
}
@@ -3638,7 +3652,7 @@
if (!cancelAndRedraw) {
mReportNextDraw = false;
mLastReportNextDrawReason = null;
- mSyncBufferCallback = null;
+ mTransactionReadyCallback = null;
mSyncBuffer = false;
if (isInLocalSync()) {
mSyncGroup.markSyncReady();
@@ -4385,7 +4399,7 @@
return false;
}
- final boolean fullRedrawNeeded = mFullRedrawNeeded || mSyncBufferCallback != null;
+ final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
@@ -4393,9 +4407,9 @@
addFrameCommitCallbackIfNeeded();
- boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null;
+ boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
if (usingAsyncReport) {
- registerCallbacksForSync(mSyncBuffer, mSyncBufferCallback);
+ registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
} else if (mHasPendingTransactions) {
// These callbacks are only needed if there's no sync involved and there were calls to
// applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4446,10 +4460,11 @@
}
if (mSurfaceHolder != null && mSurface.isValid()) {
- final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback = mSyncBufferCallback;
+ final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
+ mTransactionReadyCallback;
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
- mHandler.post(() -> syncBufferCallback.onBufferReady(null)));
- mSyncBufferCallback = null;
+ mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
+ mTransactionReadyCallback = null;
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4460,8 +4475,8 @@
}
}
}
- if (mSyncBufferCallback != null && !usingAsyncReport) {
- mSyncBufferCallback.onBufferReady(null);
+ if (mTransactionReadyCallback != null && !usingAsyncReport) {
+ mTransactionReadyCallback.onTransactionReady(null);
}
if (mPerformContentCapture) {
performContentCaptureInitialReport();
@@ -5649,17 +5664,23 @@
break;
}
case MSG_SHOW_INSETS: {
+ final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
if (mView == null) {
Log.e(TAG,
String.format("Calling showInsets(%d,%b) on window that no longer"
+ " has views.", msg.arg1, msg.arg2 == 1));
}
clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
- mInsetsController.show(msg.arg1, msg.arg2 == 1);
+ mInsetsController.show(msg.arg1, msg.arg2 == 1, statsToken);
break;
}
case MSG_HIDE_INSETS: {
- mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+ final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
+ mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
break;
}
case MSG_WINDOW_MOVED:
@@ -8225,7 +8246,7 @@
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
- mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+ mInvCompatScale = 1f / mTmpFrames.compatScale;
CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);
@@ -8811,12 +8832,14 @@
mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
}
- private void showInsets(@InsetsType int types, boolean fromIme) {
- mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+ private void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
+ mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
}
- private void hideInsets(@InsetsType int types, boolean fromIme) {
- mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+ private void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
+ mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
}
public void dispatchMoved(int newX, int newY) {
@@ -9114,6 +9137,9 @@
mConsumeBatchedInputScheduled = true;
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
mConsumedBatchedInputRunnable, null);
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.notifyCallbackPending();
+ }
}
}
@@ -9307,6 +9333,9 @@
mViews.add(view);
postIfNeededLocked();
}
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.notifyCallbackPending();
+ }
}
public void addViewRect(AttachInfo.InvalidateInfo info) {
@@ -9314,6 +9343,9 @@
mViewRects.add(info);
postIfNeededLocked();
}
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.notifyCallbackPending();
+ }
}
public void removeView(View view) {
@@ -10180,7 +10212,8 @@
}
@Override
- public void showInsets(@InsetsType int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#showInsets",
@@ -10188,13 +10221,16 @@
null /* icProto */);
}
if (viewAncestor != null) {
- viewAncestor.showInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+ viewAncestor.showInsets(types, fromIme, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
}
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
-
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#hideInsets",
@@ -10202,7 +10238,10 @@
null /* icProto */);
}
if (viewAncestor != null) {
- viewAncestor.hideInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+ viewAncestor.hideInsets(types, fromIme, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
}
}
@@ -10691,6 +10730,25 @@
.notifyOutsideTouchClientThread();
}
}
+
+ @Override
+ public void takeScreenshotOfWindow(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .takeScreenshotOfWindowClientThread(interactionId, listener, callback);
+ } else {
+ try {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+ interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
}
/**
@@ -11097,7 +11155,7 @@
}
private void registerCallbacksForSync(boolean syncBuffer,
- final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
+ final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
if (!isHardwareEnabled()) {
return;
}
@@ -11124,7 +11182,7 @@
// pendingDrawFinished.
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
- syncBufferCallback.onBufferReady(
+ transactionReadyCallback.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return null;
}
@@ -11134,7 +11192,8 @@
}
if (syncBuffer) {
- mBlastBufferQueue.syncNextTransaction(syncBufferCallback::onBufferReady);
+ mBlastBufferQueue.syncNextTransaction(
+ transactionReadyCallback::onTransactionReady);
}
return didProduceBuffer -> {
@@ -11154,7 +11213,7 @@
// since the frame didn't draw on this vsync. It's possible the frame will
// draw later, but it's better to not be sync than to block on a frame that
// may never come.
- syncBufferCallback.onBufferReady(
+ transactionReadyCallback.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return;
}
@@ -11163,22 +11222,49 @@
// syncNextTransaction callback. Instead, just report back to the Syncer so it
// knows that this sync request is complete.
if (!syncBuffer) {
- syncBufferCallback.onBufferReady(null);
+ transactionReadyCallback.onTransactionReady(null);
}
};
}
});
}
+ private final Executor mPostAtFrontExecutor = new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ mHandler.postAtFrontOfQueue(command);
+ }
+ };
+
public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
@Override
- public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
- readyToSync(syncBufferCallback);
+ public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+ updateSyncInProgressCount(parentSyncGroup);
+ if (!isInLocalSync()) {
+ // Always sync the buffer if the sync request did not come from VRI.
+ mSyncBuffer = true;
+ }
+ if (mAttachInfo.mThreadedRenderer != null) {
+ HardwareRenderer.setRtAnimationsEnabled(false);
+ }
+
+ if (mTransactionReadyCallback != null) {
+ Log.d(mTag, "Already set sync for the next draw.");
+ mTransactionReadyCallback.onTransactionReady(null);
+ }
+ if (DEBUG_BLAST) {
+ Log.d(mTag, "Setting syncFrameCallback");
+ }
+ mTransactionReadyCallback = transactionReadyCallback;
+ if (!mIsInTraversal && !mTraversalScheduled) {
+ scheduleTraversals();
+ }
}
- @Override
- public void onSyncComplete() {
- mHandler.postAtFrontOfQueue(() -> {
+ private void updateSyncInProgressCount(SurfaceSyncGroup parentSyncGroup) {
+ mNumSyncsInProgress++;
+ parentSyncGroup.addSyncCompleteCallback(mPostAtFrontExecutor, () -> {
if (--mNumSyncsInProgress == 0 && mAttachInfo.mThreadedRenderer != null) {
HardwareRenderer.setRtAnimationsEnabled(true);
}
@@ -11191,29 +11277,6 @@
return mSyncTarget;
}
- private void readyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
- mNumSyncsInProgress++;
- if (!isInLocalSync()) {
- // Always sync the buffer if the sync request did not come from VRI.
- mSyncBuffer = true;
- }
- if (mAttachInfo.mThreadedRenderer != null) {
- HardwareRenderer.setRtAnimationsEnabled(false);
- }
-
- if (mSyncBufferCallback != null) {
- Log.d(mTag, "Already set sync for the next draw.");
- mSyncBufferCallback.onBufferReady(null);
- }
- if (DEBUG_BLAST) {
- Log.d(mTag, "Setting syncFrameCallback");
- }
- mSyncBufferCallback = syncBufferCallback;
- if (!mIsInTraversal && !mTraversalScheduled) {
- scheduleTraversals();
- }
- }
-
void mergeSync(SurfaceSyncGroup otherSyncGroup) {
if (!isInLocalSync()) {
return;
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 2a76c4e..d77e499 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1425,8 +1425,8 @@
static final int WINDOW_DECOR = 1 << 8;
- static final int GENERIC_OVERLAYS = 1 << 9;
- static final int LAST = GENERIC_OVERLAYS;
+ static final int SYSTEM_OVERLAYS = 1 << 9;
+ static final int LAST = SYSTEM_OVERLAYS;
static final int SIZE = 10;
static final int DEFAULT_VISIBLE = ~IME;
@@ -1451,7 +1451,7 @@
return 7;
case WINDOW_DECOR:
return 8;
- case GENERIC_OVERLAYS:
+ case SYSTEM_OVERLAYS:
return 9;
default:
throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
@@ -1489,8 +1489,8 @@
if ((types & WINDOW_DECOR) != 0) {
result.append("windowDecor |");
}
- if ((types & GENERIC_OVERLAYS) != 0) {
- result.append("genericOverlays |");
+ if ((types & SYSTEM_OVERLAYS) != 0) {
+ result.append("systemOverlays |");
}
if (result.length() > 0) {
result.delete(result.length() - 2, result.length());
@@ -1505,7 +1505,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR,
SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT,
- GENERIC_OVERLAYS})
+ SYSTEM_OVERLAYS})
public @interface InsetsType {
}
@@ -1593,11 +1593,27 @@
}
/**
+ * System overlays represent the insets caused by the system visible elements. Unlike
+ * {@link #navigationBars()} or {@link #statusBars()}, system overlays might not be
+ * hidden by the client.
+ *
+ * For compatibility reasons, this type is included in {@link #systemBars()}. In this
+ * way, views which fit {@link #systemBars()} fit {@link #systemOverlays()}.
+ *
+ * Examples include climate controls, multi-tasking affordances, etc.
+ *
+ * @return An insets type representing the system overlays.
+ */
+ public static @InsetsType int systemOverlays() {
+ return SYSTEM_OVERLAYS;
+ }
+
+ /**
* @return All system bars. Includes {@link #statusBars()}, {@link #captionBar()} as well as
- * {@link #navigationBars()}, but not {@link #ime()}.
+ * {@link #navigationBars()}, {@link #systemOverlays()}, but not {@link #ime()}.
*/
public static @InsetsType int systemBars() {
- return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | GENERIC_OVERLAYS;
+ return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | SYSTEM_OVERLAYS;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc85181..67a6e89 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -819,25 +819,36 @@
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
/**
- * Application level {@link android.content.pm.PackageManager.Property} tag for developers to
- * provide consent for their app to allow OEMs to manually provide ActivityEmbedding split
- * rule configuration on behalf of the app.
+ * Application-level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that specifies whether OEMs are permitted to provide activity
+ * embedding split-rule configurations on behalf of the app.
*
- * <p>If {@code true}, the system can override the windowing behaviors for the app, such as
- * showing some activities side-by-side. In this case, it will report that ActivityEmbedding
- * APIs are disabled for the app to avoid conflict.
+ * <p>If {@code true}, the system is permitted to override the app's
+ * windowing behavior and implement activity embedding split rules, such as
+ * displaying activities side by side. A system override informs the app
+ * that the activity embedding APIs are disabled so the app will not provide
+ * its own activity embedding rules, which would conflict with the system's
+ * rules.
*
- * <p>If {@code false}, the system can't override the window behavior for the app. It should
- * be used if the app wants to provide their own ActivityEmbedding split rules, or if the app
- * wants to opt-out of system overrides for any other reason.
+ * <p>If {@code false}, the system is not permitted to override the
+ * windowing behavior of the app. Set the property to {@code false} if the
+ * app provides its own activity embedding split rules, or if you want to
+ * prevent the system override for any other reason.
*
- * <p>Default is {@code false}.
+ * <p>The default value is {@code false}.
*
- * <p>The system enforcement is added in Android 14, but some devices may start following the
- * requirement before that. The best practice for apps is to always explicitly set this
- * property in AndroidManifest instead of relying on the default value.
+ * <p class="note"><b>Note:</b> Refusal to permit the system override is not
+ * enforceable. OEMs can override the app's activity embedding
+ * implementation whether or not this property is specified and set to
+ * <code>false</code>. The property is, in effect, a hint to OEMs.
*
- * <p>Example usage:
+ * <p>OEMs can implement activity embedding on any API level. The best
+ * practice for apps is to always explicitly set this property in the app
+ * manifest file regardless of targeted API level rather than rely on the
+ * default value.
+ *
+ * <p><b>Syntax:</b>
* <pre>
* <application>
* <property
@@ -3408,6 +3419,11 @@
* alt="Screenshot of an activity on a display with a cutout on the long edge in portrait,
* letterbox is applied."/>
*
+ * <p>
+ * Note: Android might not allow the content view to overlap the system bars in view level.
+ * To override this behavior and allow content to be able to extend into the cutout area,
+ * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+ *
* @see DisplayCutout
* @see WindowInsets#getDisplayCutout()
* @see #layoutInDisplayCutoutMode
@@ -3443,6 +3459,11 @@
* In this mode, the window extends under cutouts on the all edges of the display in both
* portrait and landscape, regardless of whether the window is hiding the system bars.
*
+ * <p>
+ * Note: Android might not allow the content view to overlap the system bars in view level.
+ * To override this behavior and allow content to be able to extend into the cutout area,
+ * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+ *
* @see DisplayCutout
* @see WindowInsets#getDisplayCutout()
* @see #layoutInDisplayCutoutMode
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index b0cf504..a52a99b 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -24,6 +24,7 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
+import android.view.View;
import com.android.internal.util.BitUtils;
@@ -519,6 +520,9 @@
/**
* Represents the event of an application making an announcement.
+ * <p>
+ * In general, follow the practices described in
+ * {@link View#announceForAccessibility(CharSequence)}.
*/
public static final int TYPE_ANNOUNCEMENT = 0x00004000;
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index e3ffc9d..7030ab5 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -22,7 +22,9 @@
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -31,17 +33,21 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseLongArray;
import android.view.Display;
import android.view.ViewConfiguration;
+import android.window.ScreenCapture;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -53,6 +59,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -146,6 +153,10 @@
private boolean mPerformAccessibilityActionResult;
+ // SparseArray of interaction ID -> screenshot executor+callback.
+ private final SparseArray<Pair<Executor, AccessibilityService.TakeScreenshotCallback>>
+ mTakeScreenshotOfWindowCallbacks = new SparseArray<>();
+
private Message mSameThreadMessage;
private int mInteractionIdWaitingForPrefetchResult = -1;
@@ -779,6 +790,59 @@
}
/**
+ * Takes a screenshot of the window with the provided {@code accessibilityWindowId} and
+ * returns the answer asynchronously. This async behavior is similar to {@link
+ * AccessibilityService#takeScreenshot} but unlike other methods in this class which perform
+ * synchronous waiting in the AccessibilityService client.
+ *
+ * @see AccessibilityService#takeScreenshotOfWindow
+ */
+ public void takeScreenshotOfWindow(int connectionId, int accessibilityWindowId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AccessibilityService.TakeScreenshotCallback callback) {
+ synchronized (mInstanceLock) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection == null) {
+ executor.execute(() -> callback.onFailure(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+ return;
+ }
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ mTakeScreenshotOfWindowCallbacks.put(interactionId,
+ Pair.create(executor, callback));
+ // Create a ScreenCaptureListener to receive the screenshot directly from
+ // SurfaceFlinger instead of requiring an extra IPC from the app:
+ // A11yService -> App -> SurfaceFlinger -> A11yService
+ ScreenCapture.ScreenCaptureListener listener =
+ new ScreenCapture.ScreenCaptureListener(
+ screenshot -> sendWindowScreenshotSuccess(screenshot,
+ interactionId));
+ connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
+ listener, this);
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ synchronized (mInstanceLock) {
+ // Notify failure if we still haven't sent a response after timeout.
+ if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+ sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+ interactionId);
+ }
+ }
+ }, TIMEOUT_INTERACTION_MILLIS);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ } catch (RemoteException re) {
+ executor.execute(() -> callback.onFailure(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+ }
+ }
+ }
+
+ /**
* Finds {@link AccessibilityNodeInfo}s by View text. The match is case
* insensitive containment. The search is performed in the window whose
* id is specified and starts from the node whose accessibility id is
@@ -1254,6 +1318,55 @@
}
/**
+ * Sends the result of a window screenshot request to the requesting client.
+ *
+ * {@link #takeScreenshotOfWindow} does not perform synchronous waiting, so this method
+ * does not notify any wait lock.
+ */
+ private void sendWindowScreenshotSuccess(ScreenCapture.ScreenshotHardwareBuffer screenshot,
+ int interactionId) {
+ if (screenshot == null) {
+ sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+ return;
+ }
+ synchronized (mInstanceLock) {
+ if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+ final AccessibilityService.ScreenshotResult result =
+ new AccessibilityService.ScreenshotResult(screenshot.getHardwareBuffer(),
+ screenshot.getColorSpace(), SystemClock.uptimeMillis());
+ final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+ mTakeScreenshotOfWindowCallbacks.get(interactionId);
+ final Executor executor = pair.first;
+ final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+ executor.execute(() -> callback.onSuccess(result));
+ mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+ }
+ }
+ }
+
+ /**
+ * Sends an error code for a window screenshot request to the requesting client.
+ *
+ * @param errorCode The error code from {@link AccessibilityService.ScreenshotErrorCode}.
+ * @param interactionId The interaction id of the request.
+ */
+ @Override
+ public void sendTakeScreenshotOfWindowError(
+ @AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
+ synchronized (mInstanceLock) {
+ if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+ final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+ mTakeScreenshotOfWindowCallbacks.get(interactionId);
+ final Executor executor = pair.first;
+ final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+ executor.execute(() -> callback.onFailure(errorCode));
+ mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+ }
+ }
+ }
+
+ /**
* Clears the result state.
*/
private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index d07a797..88adb2e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -127,6 +127,16 @@
/** @hide */
public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
+ /**
+ * The default value for {@link #getMinMillisBetweenContentChanges};
+ */
+ public static final int UNDEFINED_MIN_MILLIS_BETWEEN_CONTENT_CHANGES = -1;
+
+ /**
+ * The minimum value for {@link #setMinMillisBetweenContentChanges};
+ */
+ public static final int MINIMUM_MIN_MILLIS_BETWEEN_CONTENT_CHANGES = 100;
+
/** @hide */
public static final long ROOT_NODE_ID = makeNodeId(ROOT_ITEM_ID,
AccessibilityNodeProvider.HOST_VIEW_ID);
@@ -879,6 +889,9 @@
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
+ private int mMinMillisBetweenContentChanges =
+ UNDEFINED_MIN_MILLIS_BETWEEN_CONTENT_CHANGES;
+
private int mBooleanProperties;
private final Rect mBoundsInParent = new Rect();
private final Rect mBoundsInScreen = new Rect();
@@ -897,6 +910,7 @@
private CharSequence mTooltipText;
private String mViewIdResourceName;
private String mUniqueId;
+ private CharSequence mContainerTitle;
private ArrayList<String> mExtraDataKeys;
@UnsupportedAppUsage
@@ -1781,6 +1795,42 @@
}
/**
+ * Sets the minimum time duration between two content change events, which is used in throttling
+ * content change events in accessibility services.
+ *
+ * <p>
+ * <strong>Note:</strong>
+ * This value should not be smaller than {@link #MINIMUM_MIN_MILLIS_BETWEEN_CONTENT_CHANGES},
+ * otherwise it would be ignored by accessibility services.
+ * </p>
+ *
+ * <p>
+ * Example: An app can set MinMillisBetweenContentChanges as 1 min for a view which sends
+ * content change events to accessibility services one event per second.
+ * Accessibility service will throttle those content change events and only handle one event
+ * per minute for that view.
+ * </p>
+ *
+ * @see AccessibilityEvent#getContentChangeTypes for all content change types.
+ * @param minMillisBetweenContentChanges the minimum duration between content change events.
+ */
+ public void setMinMillisBetweenContentChanges(int minMillisBetweenContentChanges) {
+ enforceNotSealed();
+ mMinMillisBetweenContentChanges = minMillisBetweenContentChanges
+ >= MINIMUM_MIN_MILLIS_BETWEEN_CONTENT_CHANGES
+ ? minMillisBetweenContentChanges
+ : UNDEFINED_MIN_MILLIS_BETWEEN_CONTENT_CHANGES;
+ }
+
+ /**
+ * Gets the minimum time duration between two content change events. This method may return
+ * {@link #UNDEFINED_MIN_MILLIS_BETWEEN_CONTENT_CHANGES}
+ */
+ public int getMinMillisBetweenContentChanges() {
+ return mMinMillisBetweenContentChanges;
+ }
+
+ /**
* Performs an action on the node.
* <p>
* <strong>Note:</strong> An action can be performed only if the request is made
@@ -3631,6 +3681,47 @@
}
/**
+ * Sets the container title for app-developer-defined container which can be any type of
+ * ViewGroup or layout.
+ * Container title will be used to group together related controls, similar to HTML fieldset.
+ * Or container title may identify a large piece of the UI that is visibly grouped together,
+ * such as a toolbar or a card, etc.
+ * <p>
+ * Container title helps to assist in navigation across containers and other groups.
+ * For example, a screen reader may use this to determine where to put accessibility focus.
+ * </p>
+ * <p>
+ * Container title is different from pane title{@link #setPaneTitle} which indicates that the
+ * node represents a window or activity.
+ * </p>
+ *
+ * <p>
+ * Example: An app can set container titles on several non-modal menus, containing TextViews
+ * or ImageButtons that have content descriptions, text, etc. Screen readers can quickly
+ * switch accessibility focus among menus instead of child views. Other accessibility-services
+ * can easily find the menu.
+ * </p>
+ *
+ * @param containerTitle The container title that is associated with a ViewGroup/Layout on the
+ * screen.
+ */
+ public void setContainerTitle(@Nullable CharSequence containerTitle) {
+ enforceNotSealed();
+ mContainerTitle = (containerTitle == null) ? null
+ : containerTitle.subSequence(0, containerTitle.length());
+ }
+
+ /**
+ * Returns the container title.
+ *
+ * @see #setContainerTitle for details.
+ */
+ @Nullable
+ public CharSequence getContainerTitle() {
+ return mContainerTitle;
+ }
+
+ /**
* Sets the token and node id of the leashed parent.
*
* @param token The token.
@@ -3909,6 +4000,11 @@
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
+ if (mMinMillisBetweenContentChanges
+ != DEFAULT.mMinMillisBetweenContentChanges) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
if (!LongArray.elementsEqual(mChildNodeIds, DEFAULT.mChildNodeIds)) {
@@ -3963,6 +4059,10 @@
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
+ if (!Objects.equals(mContainerTitle, DEFAULT.mContainerTitle)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
nonDefaultFields |= bitAt(fieldIndex);
}
@@ -4034,6 +4134,9 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mMinMillisBetweenContentChanges);
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
@@ -4108,10 +4211,10 @@
}
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mContainerTitle);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mUniqueId);
-
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionEnd);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mInputType);
@@ -4189,6 +4292,7 @@
mLabeledById = other.mLabeledById;
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
+ mMinMillisBetweenContentChanges = other.mMinMillisBetweenContentChanges;
mWindowId = other.mWindowId;
mConnectionId = other.mConnectionId;
mUniqueId = other.mUniqueId;
@@ -4204,6 +4308,7 @@
mContentDescription = other.mContentDescription;
mPaneTitle = other.mPaneTitle;
mTooltipText = other.mTooltipText;
+ mContainerTitle = other.mContainerTitle;
mViewIdResourceName = other.mViewIdResourceName;
if (mActions != null) mActions.clear();
@@ -4291,6 +4396,9 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mMinMillisBetweenContentChanges = parcel.readInt();
+ }
if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
@@ -4347,6 +4455,7 @@
}
if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mContainerTitle = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
if (isBitSet(nonDefaultFields, fieldIndex++)) mUniqueId = parcel.readString();
@@ -4638,6 +4747,8 @@
builder.append("; mParentNodeId: 0x").append(Long.toHexString(mParentNodeId));
builder.append("; traversalBefore: 0x").append(Long.toHexString(mTraversalBefore));
builder.append("; traversalAfter: 0x").append(Long.toHexString(mTraversalAfter));
+ builder.append("; minMillisBetweenContentChanges: ")
+ .append(mMinMillisBetweenContentChanges);
int granularities = mMovementGranularities;
builder.append("; MovementGranularities: [");
@@ -4675,6 +4786,7 @@
builder.append("; stateDescription: ").append(mStateDescription);
builder.append("; contentDescription: ").append(mContentDescription);
builder.append("; tooltipText: ").append(mTooltipText);
+ builder.append("; containerTitle: ").append(mContainerTitle);
builder.append("; viewIdResName: ").append(mViewIdResourceName);
builder.append("; uniqueId: ").append(mUniqueId);
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 472a363..fb01921 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -22,6 +22,7 @@
import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
/**
* Interface for interaction between the AccessibilityManagerService
@@ -60,4 +61,8 @@
void clearAccessibilityFocus();
void notifyOutsideTouch();
+
+ void takeScreenshotOfWindow(int interactionId,
+ in ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 231e75a..456bf58 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -63,4 +63,9 @@
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+
+ /**
+ * Sends an error code for a window screenshot request to the requesting client.
+ */
+ void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index ef683b7..a92bc94 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1197,6 +1197,9 @@
* @hide
*/
public void notifyViewEnteredForFillDialog(View v) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
synchronized (mLock) {
if (mTrackedViews != null) {
// To support the fill dialog can show for the autofillable Views in
@@ -1218,13 +1221,18 @@
Log.d(TAG, "Trigger fill request at view entered");
}
- // Note: No need for atomic getAndSet as this method is called on the UI thread.
- mIsFillRequested.set(true);
-
int flags = FLAG_SUPPORTS_FILL_DIALOG;
flags |= FLAG_VIEW_NOT_FOCUSED;
- // use root view, so autofill UI does not trigger immediately.
- notifyViewEntered(v.getRootView(), flags);
+
+ synchronized (mLock) {
+ // To match the id of the IME served view, used AutofillId.NO_AUTOFILL_ID on prefill
+ // request, because IME will reset the id of IME served view to 0 when activity
+ // start and does not focus on any view. If the id of the prefill request is
+ // not match to the IME served view's, Autofill will be blocking to wait inline
+ // request from the IME.
+ notifyViewEnteredLocked(/* view= */ null, AutofillId.NO_AUTOFILL_ID,
+ /* bounds= */ null, /* value= */ null, flags);
+ }
}
}
@@ -1233,6 +1241,8 @@
}
private int getImeStateFlag(View v) {
+ if (v == null) return 0;
+
final WindowInsets rootWindowInsets = v.getRootWindowInsets();
if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
return FLAG_IME_SHOWING;
@@ -1280,8 +1290,8 @@
}
AutofillCallback callback;
synchronized (mLock) {
- mIsFillRequested.set(true);
- callback = notifyViewEnteredLocked(view, flags);
+ callback = notifyViewEnteredLocked(
+ view, view.getAutofillId(), /* bounds= */ null, view.getAutofillValue(), flags);
}
if (callback != null) {
@@ -1289,62 +1299,6 @@
}
}
- /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
- @GuardedBy("mLock")
- private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) {
- final AutofillId id = view.getAutofillId();
- if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
-
- AutofillCallback callback = null;
-
- final boolean clientAdded = tryAddServiceClientIfNeededLocked();
-
- if (!clientAdded) {
- if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client");
- return callback;
- }
-
- if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
- if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
-
- if (mCallback != null) {
- callback = mCallback;
- }
- } else {
- // don't notify entered when Activity is already in background
- if (!isClientDisablingEnterExitEvent()) {
- final AutofillValue value = view.getAutofillValue();
-
- if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
- flags |= FLAG_PASSWORD_INPUT_TYPE;
- }
-
- flags |= getImeStateFlag(view);
-
- if (!isActiveLocked()) {
- // Starts new session.
- startSessionLocked(id, null, value, flags);
- } else {
- // Update focus on existing session.
- if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
- if (sDebug) {
- Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
- + "mForAugmentedAutofillOnly on manual request");
- }
- mForAugmentedAutofillOnly = false;
- }
-
- if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) {
- flags |= FLAG_RESET_FILL_DIALOG_STATE;
- }
- updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
- }
- addEnteredIdLocked(id);
- }
- }
- return callback;
- }
-
/**
* Called when a {@link View} that supports autofill is exited.
*
@@ -1461,9 +1415,11 @@
if (!hasAutofillFeature()) {
return;
}
+
AutofillCallback callback;
synchronized (mLock) {
- callback = notifyViewEnteredLocked(view, virtualId, bounds, flags);
+ callback = notifyViewEnteredLocked(
+ view, getAutofillId(view, virtualId), bounds, /* value= */ null, flags);
}
if (callback != null) {
@@ -1474,53 +1430,55 @@
/** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
@GuardedBy("mLock")
- private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds,
- int flags) {
- final AutofillId id = getAutofillId(view, virtualId);
- AutofillCallback callback = null;
- if (shouldIgnoreViewEnteredLocked(id, flags)) return callback;
+ private AutofillCallback notifyViewEnteredLocked(@Nullable View view, AutofillId id,
+ Rect bounds, AutofillValue value, int flags) {
+ if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
final boolean clientAdded = tryAddServiceClientIfNeededLocked();
-
if (!clientAdded) {
if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client");
- return callback;
+ return null;
}
if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
if (sVerbose) {
Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
}
- if (mCallback != null) {
- callback = mCallback;
- }
- } else {
- // don't notify entered when Activity is already in background
- if (!isClientDisablingEnterExitEvent()) {
- if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
- flags |= FLAG_PASSWORD_INPUT_TYPE;
- }
-
- flags |= getImeStateFlag(view);
-
- if (!isActiveLocked()) {
- // Starts new session.
- startSessionLocked(id, bounds, null, flags);
- } else {
- // Update focus on existing session.
- if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
- if (sDebug) {
- Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
- + "mForAugmentedAutofillOnly on manual request");
- }
- mForAugmentedAutofillOnly = false;
- }
- updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
- }
- addEnteredIdLocked(id);
- }
+ return mCallback;
}
- return callback;
+
+ mIsFillRequested.set(true);
+
+ // don't notify entered when Activity is already in background
+ if (!isClientDisablingEnterExitEvent()) {
+ if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
+ flags |= FLAG_PASSWORD_INPUT_TYPE;
+ }
+
+ flags |= getImeStateFlag(view);
+
+ if (!isActiveLocked()) {
+ // Starts new session.
+ startSessionLocked(id, bounds, value, flags);
+ } else {
+ // Update focus on existing session.
+ if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
+ if (sDebug) {
+ Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
+ + "mForAugmentedAutofillOnly on manual request");
+ }
+ mForAugmentedAutofillOnly = false;
+ }
+
+ if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) {
+ flags |= FLAG_RESET_FILL_DIALOG_STATE;
+ }
+
+ updateSessionLocked(id, bounds, value, ACTION_VIEW_ENTERED, flags);
+ }
+ addEnteredIdLocked(id);
+ }
+ return null;
}
@GuardedBy("mLock")
@@ -2025,7 +1983,8 @@
if (!mOnInvisibleCalled && focusView != null
&& focusView.canNotifyAutofillEnterExitEvent()) {
notifyViewExitedLocked(focusView);
- notifyViewEnteredLocked(focusView, 0);
+ notifyViewEnteredLocked(focusView, focusView.getAutofillId(),
+ /* bounds= */ null, focusView.getAutofillValue(), /* flags= */ 0);
}
if (data == null) {
// data is set to null when result is not RESULT_OK
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index a66c67b..6eae63a 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -275,15 +275,16 @@
@AnyThread
static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
- int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+ @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
+ @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
final IInputMethodManager service = getService();
if (service == null) {
return false;
}
try {
- return service.showSoftInput(
- client, windowToken, flags, lastClickToolType, resultReceiver, reason);
+ return service.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
+ resultReceiver, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -291,14 +292,15 @@
@AnyThread
static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
- int flags, @Nullable ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ @Nullable ImeTracker.Token statsToken, int flags,
+ @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
final IInputMethodManager service = getService();
if (service == null) {
return false;
}
try {
- return service.hideSoftInput(client, windowToken, flags, resultReceiver, reason);
+ return service.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+ reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/core/java/android/view/inputmethod/ImeTracker.aidl
similarity index 79%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to core/java/android/view/inputmethod/ImeTracker.aidl
index 817c209f..1988f48 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/core/java/android/view/inputmethod/ImeTracker.aidl
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package android.view.inputmethod;
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
+parcelable ImeTracker.Token;
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
new file mode 100644
index 0000000..f4ecdff
--- /dev/null
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -0,0 +1,488 @@
+/*
+ * 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 android.view.inputmethod;
+
+import static android.view.inputmethod.ImeTracker.Debug.originToString;
+import static android.view.inputmethod.ImeTracker.Debug.phaseToString;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+/** @hide */
+public interface ImeTracker {
+
+ String TAG = "ImeTracker";
+
+ /**
+ * The origin of the IME request
+ *
+ * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+ * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+ */
+ @IntDef(prefix = { "ORIGIN_" }, value = {
+ ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ ORIGIN_SERVER_START_INPUT,
+ ORIGIN_SERVER_HIDE_INPUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Origin {}
+
+ /**
+ * The IME show request originated in the client.
+ */
+ int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
+
+ /**
+ * The IME hide request originated in the client.
+ */
+ int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
+
+ /**
+ * The IME show request originated in the server.
+ */
+ int ORIGIN_SERVER_START_INPUT = 2;
+
+ /**
+ * The IME hide request originated in the server.
+ */
+ int ORIGIN_SERVER_HIDE_INPUT = 3;
+
+ /**
+ * The current phase of the IME request.
+ *
+ * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+ * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+ */
+ @IntDef(prefix = { "PHASE_" }, value = {
+ PHASE_CLIENT_VIEW_SERVED,
+ PHASE_SERVER_CLIENT_KNOWN,
+ PHASE_SERVER_CLIENT_FOCUSED,
+ PHASE_SERVER_ACCESSIBILITY,
+ PHASE_SERVER_SYSTEM_READY,
+ PHASE_SERVER_HIDE_IMPLICIT,
+ PHASE_SERVER_HIDE_NOT_ALWAYS,
+ PHASE_SERVER_WAIT_IME,
+ PHASE_SERVER_HAS_IME,
+ PHASE_SERVER_SHOULD_HIDE,
+ PHASE_IME_WRAPPER,
+ PHASE_IME_WRAPPER_DISPATCH,
+ PHASE_IME_SHOW_SOFT_INPUT,
+ PHASE_IME_HIDE_SOFT_INPUT,
+ PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE,
+ PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER,
+ PHASE_SERVER_APPLY_IME_VISIBILITY,
+ PHASE_WM_SHOW_IME_RUNNER,
+ PHASE_WM_SHOW_IME_READY,
+ PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET,
+ PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS,
+ PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS,
+ PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS,
+ PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS,
+ PHASE_WM_REMOTE_INSETS_CONTROLLER,
+ PHASE_WM_ANIMATION_CREATE,
+ PHASE_WM_ANIMATION_RUNNING,
+ PHASE_CLIENT_SHOW_INSETS,
+ PHASE_CLIENT_HIDE_INSETS,
+ PHASE_CLIENT_HANDLE_SHOW_INSETS,
+ PHASE_CLIENT_HANDLE_HIDE_INSETS,
+ PHASE_CLIENT_APPLY_ANIMATION,
+ PHASE_CLIENT_CONTROL_ANIMATION,
+ PHASE_CLIENT_ANIMATION_RUNNING,
+ PHASE_CLIENT_ANIMATION_CANCEL,
+ PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
+ PHASE_CLIENT_ANIMATION_FINISHED_HIDE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Phase {}
+
+ /** The view that requested the IME has been served by the IMM. */
+ int PHASE_CLIENT_VIEW_SERVED = 0;
+
+ /** The IME client that requested the IME has window manager focus. */
+ int PHASE_SERVER_CLIENT_KNOWN = 1;
+
+ /** The IME client that requested the IME has IME focus. */
+ int PHASE_SERVER_CLIENT_FOCUSED = 2;
+
+ /** The IME request complies with the current accessibility settings. */
+ int PHASE_SERVER_ACCESSIBILITY = 3;
+
+ /** The server is ready to run third party code. */
+ int PHASE_SERVER_SYSTEM_READY = 4;
+
+ /** Checked the implicit hide request against any explicit show requests. */
+ int PHASE_SERVER_HIDE_IMPLICIT = 5;
+
+ /** Checked the not-always hide request against any forced show requests. */
+ int PHASE_SERVER_HIDE_NOT_ALWAYS = 6;
+
+ /** The server is waiting for a connection to the IME. */
+ int PHASE_SERVER_WAIT_IME = 7;
+
+ /** The server has a connection to the IME. */
+ int PHASE_SERVER_HAS_IME = 8;
+
+ /** The server decided the IME should be hidden. */
+ int PHASE_SERVER_SHOULD_HIDE = 9;
+
+ /** Reached the IME wrapper. */
+ int PHASE_IME_WRAPPER = 10;
+
+ /** Dispatched from the IME wrapper to the IME. */
+ int PHASE_IME_WRAPPER_DISPATCH = 11;
+
+ /** Reached the IME' showSoftInput method. */
+ int PHASE_IME_SHOW_SOFT_INPUT = 12;
+
+ /** Reached the IME' hideSoftInput method. */
+ int PHASE_IME_HIDE_SOFT_INPUT = 13;
+
+ /** The server decided the IME should be shown. */
+ int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14;
+
+ /** Requested applying the IME visibility in the insets source consumer. */
+ int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
+
+ /** Applied the IME visibility. */
+ int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
+
+ /** Created the show IME runner. */
+ int PHASE_WM_SHOW_IME_RUNNER = 17;
+
+ /** Ready to show IME. */
+ int PHASE_WM_SHOW_IME_READY = 18;
+
+ /** The Window Manager has a connection to the IME insets control target. */
+ int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19;
+
+ /** Reached the window insets control target's show insets method. */
+ int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20;
+
+ /** Reached the window insets control target's hide insets method. */
+ int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21;
+
+ /** Reached the remote insets control target's show insets method. */
+ int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22;
+
+ /** Reached the remote insets control target's hide insets method. */
+ int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23;
+
+ /** Reached the remote insets controller. */
+ int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24;
+
+ /** Created the IME window insets show animation. */
+ int PHASE_WM_ANIMATION_CREATE = 25;
+
+ /** Started the IME window insets show animation. */
+ int PHASE_WM_ANIMATION_RUNNING = 26;
+
+ /** Reached the client's show insets method. */
+ int PHASE_CLIENT_SHOW_INSETS = 27;
+
+ /** Reached the client's hide insets method. */
+ int PHASE_CLIENT_HIDE_INSETS = 28;
+
+ /** Handling the IME window insets show request. */
+ int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
+
+ /** Handling the IME window insets hide request. */
+ int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30;
+
+ /** Applied the IME window insets show animation. */
+ int PHASE_CLIENT_APPLY_ANIMATION = 31;
+
+ /** Started the IME window insets show animation. */
+ int PHASE_CLIENT_CONTROL_ANIMATION = 32;
+
+ /** Queued the IME window insets show animation. */
+ int PHASE_CLIENT_ANIMATION_RUNNING = 33;
+
+ /** Cancelled the IME window insets show animation. */
+ int PHASE_CLIENT_ANIMATION_CANCEL = 34;
+
+ /** Finished the IME window insets show animation. */
+ int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
+
+ /** Finished the IME window insets hide animation. */
+ int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36;
+
+ /**
+ * Called when an IME show request is created.
+ *
+ * @param token the token tracking the current IME show request or {@code null} otherwise.
+ * @param origin the origin of the IME show request.
+ * @param reason the reason why the IME show request was created.
+ */
+ void onRequestShow(@Nullable Token token, @Origin int origin,
+ @SoftInputShowHideReason int reason);
+
+ /**
+ * Called when an IME hide request is created.
+ *
+ * @param token the token tracking the current IME hide request or {@code null} otherwise.
+ * @param origin the origin of the IME hide request.
+ * @param reason the reason why the IME hide request was created.
+ */
+ void onRequestHide(@Nullable Token token, @Origin int origin,
+ @SoftInputShowHideReason int reason);
+
+ /**
+ * Called when an IME request progresses to a further phase.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the new phase the IME request reached.
+ */
+ void onProgress(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when an IME request fails.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the phase the IME request failed at.
+ */
+ void onFailed(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when an IME request reached a flow that is not yet implemented.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the phase the IME request was currently at.
+ */
+ void onTodo(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when an IME request is cancelled.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the phase the IME request was cancelled at.
+ */
+ void onCancelled(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when the IME show request is successful.
+ *
+ * @param token the token tracking the current IME show request or {@code null} otherwise.
+ */
+ void onShown(@Nullable Token token);
+
+ /**
+ * Called when the IME hide request is successful.
+ *
+ * @param token the token tracking the current IME hide request or {@code null} otherwise.
+ */
+ void onHidden(@Nullable Token token);
+
+ /**
+ * Get the singleton instance of this class.
+ *
+ * @return the singleton instance of this class
+ */
+ @NonNull
+ static ImeTracker get() {
+ return SystemProperties.getBoolean("persist.debug.imetracker", false)
+ ? LOGGER
+ : NOOP_LOGGER;
+ }
+
+ /** The singleton IME tracker instance. */
+ ImeTracker LOGGER = new ImeTracker() {
+
+ @Override
+ public void onRequestShow(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
+ + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+ }
+
+ @Override
+ public void onRequestHide(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
+ + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+ }
+
+ @Override
+ public void onProgress(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onFailed(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onTodo(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onCancelled(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onShown(@Nullable Token token) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onShown");
+ }
+
+ @Override
+ public void onHidden(@Nullable Token token) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onHidden");
+ }
+ };
+
+ /** The singleton no-op IME tracker instance. */
+ ImeTracker NOOP_LOGGER = new ImeTracker() {
+
+ @Override
+ public void onRequestShow(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {}
+
+ @Override
+ public void onRequestHide(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {}
+
+ @Override
+ public void onProgress(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onFailed(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onTodo(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onCancelled(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onShown(@Nullable Token token) {}
+
+ @Override
+ public void onHidden(@Nullable Token token) {}
+ };
+
+ /** A token that tracks the progress of an IME request. */
+ class Token implements Parcelable {
+
+ private final IBinder mBinder;
+ private final String mTag;
+
+ public Token() {
+ this(ActivityThread.currentProcessName());
+ }
+
+ public Token(String component) {
+ this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
+ }
+
+ private Token(IBinder binder, String tag) {
+ mBinder = binder;
+ mTag = tag;
+ }
+
+ /** For Parcelable, no special marshalled objects. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mBinder);
+ dest.writeString8(mTag);
+ }
+
+ @NonNull
+ public static final Creator<Token> CREATOR = new Creator<>() {
+ @Override
+ public Token createFromParcel(Parcel source) {
+ IBinder binder = source.readStrongBinder();
+ String tag = source.readString8();
+ return new Token(binder, tag);
+ }
+
+ @Override
+ public Token[] newArray(int size) {
+ return new Token[size];
+ }
+ };
+ }
+
+ /**
+ * Utilities for mapping phases and origins IntDef values to their names.
+ *
+ * Note: This is held in a separate class so that it only gets initialized when actually needed.
+ */
+ class Debug {
+
+ private static final Map<Integer, String> sOrigins =
+ getFieldMapping(ImeTracker.class, "ORIGIN_");
+ private static final Map<Integer, String> sPhases =
+ getFieldMapping(ImeTracker.class, "PHASE_");
+
+ public static String originToString(int origin) {
+ return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
+ }
+
+ public static String phaseToString(int phase) {
+ return sPhases.getOrDefault(phase, "PHASE_" + phase);
+ }
+
+ private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+ return Arrays.stream(cls.getDeclaredFields())
+ .filter(field -> field.getName().startsWith(fieldPrefix))
+ .collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
+ }
+
+ private static int getFieldValue(Field field) {
+ try {
+ return field.getInt(null);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index c94a372..9b519c3 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1440,6 +1440,8 @@
* that this means you can't position the cursor within the text.
* @param text the text to replace. This may include styles.
* @param textAttribute The extra information about the text. This value may be null.
+ * @return {@code true} if the replace command was sent to the associated editor (regardless of
+ * whether the replacement is success or not), {@code false} otherwise.
*/
default boolean replaceText(
@IntRange(from = 0) int start,
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 4d5a17d..92380ed 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -300,11 +300,12 @@
* @param showInputToken an opaque {@link android.os.Binder} token to identify which API call
* of {@link InputMethodManager#showSoftInput(View, int)} is associated with
* this callback.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
* @hide
*/
@MainThread
- default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder showInputToken) {
+ public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
showSoftInput(flags, resultReceiver);
}
@@ -338,11 +339,14 @@
* @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call
* of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated
* with this callback.
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
* @hide
*/
@MainThread
- public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder hideInputToken);
+ public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
+ hideSoftInput(flags, resultReceiver);
+ }
/**
* Request that any soft input part of the input method be hidden from the user.
@@ -369,7 +373,7 @@
/**
* Checks if IME is ready to start stylus handwriting session.
- * If yes, {@link #startStylusHandwriting(InputChannel, List)} is called.
+ * If yes, {@link #startStylusHandwriting(int, InputChannel, List)} is called.
* @param requestId
* @hide
*/
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 74afced..ee31fd5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -632,6 +632,8 @@
private final DelegateImpl mDelegate = new DelegateImpl();
+ private static boolean sPreventImeStartupUnlessTextEditor;
+
// -----------------------------------------------------------
private static final int MSG_DUMP = 1;
@@ -1435,6 +1437,10 @@
// display case.
final Looper looper = displayId == Display.DEFAULT_DISPLAY
? Looper.getMainLooper() : context.getMainLooper();
+ // Keep track of whether to expect the IME to be unavailable so as to avoid log spam in
+ // sendInputEventOnMainLooperLocked() by not logging a verbose message on every DPAD event
+ sPreventImeStartupUnlessTextEditor = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
return forContextInternal(displayId, looper);
}
@@ -2001,6 +2007,10 @@
private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ reason);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
null /* icProto */);
// Re-dispatch if there is a context mismatch.
@@ -2012,10 +2022,13 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view)) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
@@ -2024,6 +2037,7 @@
return IInputMethodManagerGlobalInvoker.showSoftInput(
mClient,
view.getWindowToken(),
+ statsToken,
flags,
mCurRootView.getLastClickToolType(),
resultReceiver,
@@ -2043,19 +2057,28 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
synchronized (mH) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ SoftInputShowHideReason.SHOW_SOFT_INPUT);
+
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+ " removed soon. If you are using androidx.appcompat.widget.SearchView,"
+ " please update to version 26.0 or newer version.");
if (mCurRootView == null || mCurRootView.getView() == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
return;
}
+
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
IInputMethodManagerGlobalInvoker.showSoftInput(
mClient,
mCurRootView.getView().getWindowToken(),
+ statsToken,
flags,
mCurRootView.getLastClickToolType(),
resultReceiver,
@@ -2125,17 +2148,24 @@
private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ reason);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
this, null /* icProto */);
checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return false;
}
- return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, flags,
- resultReceiver, reason);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+ return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+ flags, resultReceiver, reason);
}
}
@@ -2763,14 +2793,23 @@
@UnsupportedAppUsage
void closeCurrentInput() {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT);
+
synchronized (mH) {
if (mCurRootView == null || mCurRootView.getView() == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
return;
}
+
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
IInputMethodManagerGlobalInvoker.hideSoftInput(
mClient,
mCurRootView.getView().getWindowToken(),
+ statsToken,
HIDE_NOT_ALWAYS,
null,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
@@ -2839,15 +2878,24 @@
* @hide
*/
public void notifyImeHidden(IBinder windowToken) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
synchronized (mH) {
- if (isImeSessionAvailableLocked() && mCurRootView != null
- && mCurRootView.getWindowToken() == windowToken) {
- IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
- null /* resultReceiver */,
- SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+ if (!isImeSessionAvailableLocked() || mCurRootView == null
+ || mCurRootView.getWindowToken() != windowToken) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ return;
}
+
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+ IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+ 0 /* flags */, null /* resultReceiver */,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
}
@@ -3364,8 +3412,12 @@
return DISPATCH_IN_PROGRESS;
}
- Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
- + " dropping: " + event);
+ if (sPreventImeStartupUnlessTextEditor) {
+ Log.d(TAG, "Dropping event because IME is evicted: " + event);
+ } else {
+ Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
+ + " dropping: " + event);
+ }
}
return DISPATCH_NOT_HANDLED;
}
@@ -3967,7 +4019,7 @@
/**
* As reported by {@link InputBindResult}. This value is determined by
- * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+ * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecker}.
*/
final boolean mIsInputMethodSuppressingSpellChecker;
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index bdfcb03..6a02198 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -86,6 +86,7 @@
private final int mSubtypeHashCode;
private final int mSubtypeIconResId;
private final int mSubtypeNameResId;
+ private final CharSequence mSubtypeNameOverride;
private final int mSubtypeId;
private final String mSubtypeLocale;
private final String mSubtypeLanguageTag;
@@ -174,6 +175,21 @@
private int mSubtypeNameResId = 0;
/**
+ * Sets the untranslatable name of the subtype.
+ *
+ * This string is used as the subtype's display name if subtype's name res Id is 0.
+ *
+ * @param nameOverride is the name to set.
+ */
+ @NonNull
+ public InputMethodSubtypeBuilder setSubtypeNameOverride(
+ @NonNull CharSequence nameOverride) {
+ mSubtypeNameOverride = nameOverride;
+ return this;
+ }
+ private CharSequence mSubtypeNameOverride = "";
+
+ /**
* @param subtypeId is the unique ID for this subtype. The input method framework keeps
* track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
* stay enabled even if other attributes are different. If the ID is unspecified or 0,
@@ -229,23 +245,23 @@
public InputMethodSubtype build() {
return new InputMethodSubtype(this);
}
- }
+ }
- private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
- String mode, String extraValue, boolean isAuxiliary,
- boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
- final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
- builder.mSubtypeNameResId = nameId;
- builder.mSubtypeIconResId = iconId;
- builder.mSubtypeLocale = locale;
- builder.mSubtypeMode = mode;
- builder.mSubtypeExtraValue = extraValue;
- builder.mIsAuxiliary = isAuxiliary;
- builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
- builder.mSubtypeId = id;
- builder.mIsAsciiCapable = isAsciiCapable;
- return builder;
- }
+ private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId,
+ String locale, String mode, String extraValue, boolean isAuxiliary,
+ boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
+ final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
+ builder.mSubtypeNameResId = nameId;
+ builder.mSubtypeIconResId = iconId;
+ builder.mSubtypeLocale = locale;
+ builder.mSubtypeMode = mode;
+ builder.mSubtypeExtraValue = extraValue;
+ builder.mIsAuxiliary = isAuxiliary;
+ builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+ builder.mSubtypeId = id;
+ builder.mIsAsciiCapable = isAsciiCapable;
+ return builder;
+ }
/**
* Constructor with no subtype ID specified.
@@ -305,6 +321,7 @@
*/
private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
mSubtypeNameResId = builder.mSubtypeNameResId;
+ mSubtypeNameOverride = builder.mSubtypeNameOverride;
mSubtypeIconResId = builder.mSubtypeIconResId;
mSubtypeLocale = builder.mSubtypeLocale;
mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
@@ -327,6 +344,8 @@
InputMethodSubtype(Parcel source) {
String s;
mSubtypeNameResId = source.readInt();
+ CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mSubtypeNameOverride = cs != null ? cs : "";
mSubtypeIconResId = source.readInt();
s = source.readString();
mSubtypeLocale = s != null ? s : "";
@@ -351,6 +370,14 @@
}
/**
+ * @return The subtype's untranslatable name string.
+ */
+ @NonNull
+ public CharSequence getNameOverride() {
+ return mSubtypeNameOverride;
+ }
+
+ /**
* @return Resource ID of the subtype icon drawable.
*/
public int getIconResId() {
@@ -532,8 +559,11 @@
public CharSequence getDisplayName(
Context context, String packageName, ApplicationInfo appInfo) {
if (mSubtypeNameResId == 0) {
- return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(),
- DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
+ return TextUtils.isEmpty(mSubtypeNameOverride)
+ ? getLocaleDisplayName(
+ getLocaleFromContext(context), getLocaleObject(),
+ DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)
+ : mSubtypeNameOverride;
}
final CharSequence subtypeName = context.getPackageManager().getText(
@@ -698,6 +728,7 @@
@Override
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
+ TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags);
dest.writeInt(mSubtypeIconResId);
dest.writeString(mSubtypeLocale);
dest.writeString(mSubtypeLanguageTag);
@@ -765,4 +796,4 @@
}
return sortedList;
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 1df4fc5..500c41c 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -16,12 +16,13 @@
package android.view.inputmethod;
+import static android.graphics.Typeface.NORMAL;
+
import android.annotation.ColorInt;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
-import android.content.res.ColorStateList;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.fonts.FontStyle;
@@ -30,7 +31,7 @@
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.InputFilter;
+import android.text.method.TransformationMethod;
import android.widget.TextView;
import java.util.Objects;
@@ -38,7 +39,6 @@
/**
* Information about text appearance in an editor, passed through
* {@link CursorAnchorInfo} for use by {@link InputMethodService}.
- *
* @see TextView
* @see Paint
* @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo)
@@ -46,12 +46,12 @@
*/
public final class TextAppearanceInfo implements Parcelable {
/**
- * The text size (in pixels) for current {@link TextView}.
+ * The text size (in pixels) for current editor.
*/
private final @Px float mTextSize;
/**
- * The LocaleList of the text.
+ * The {@link LocaleList} of the text.
*/
@NonNull private final LocaleList mTextLocales;
@@ -64,7 +64,8 @@
/**
* The weight of the text.
*/
- private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight;
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ private final int mTextFontWeight;
/**
* The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}.
@@ -72,8 +73,7 @@
private final @Typeface.Style int mTextStyle;
/**
- * Whether the transformation method applied to the current {@link TextView} is set to
- * ALL CAPS.
+ * Whether the transformation method applied to the current editor is set to all caps.
*/
private final boolean mAllCaps;
@@ -93,6 +93,11 @@
private final @Px float mShadowRadius;
/**
+ * The shadow color of the text shadow.
+ */
+ private final @ColorInt int mShadowColor;
+
+ /**
* The elegant text height, especially for less compacted complex script text.
*/
private final boolean mElegantTextHeight;
@@ -135,67 +140,46 @@
/**
* The color of the text selection highlight.
*/
- private final @ColorInt int mTextColorHighlight;
+ private final @ColorInt int mHighlightTextColor;
/**
- * The current text color.
+ * The current text color of the editor.
*/
private final @ColorInt int mTextColor;
/**
- * The current color of the hint text.
+ * The current color of the hint text.
*/
- private final @ColorInt int mTextColorHint;
+ private final @ColorInt int mHintTextColor;
/**
- * The text color for links.
+ * The text color used to paint the links in the editor.
*/
- @Nullable private final ColorStateList mTextColorLink;
+ private final @ColorInt int mLinkTextColor;
- /**
- * The max length of text.
- */
- private final int mMaxLength;
-
-
- public TextAppearanceInfo(@NonNull TextView textView) {
- mTextSize = textView.getTextSize();
- mTextLocales = textView.getTextLocales();
- Typeface typeface = textView.getPaint().getTypeface();
- String systemFontFamilyName = null;
- int textFontWeight = -1;
- if (typeface != null) {
- systemFontFamilyName = typeface.getSystemFontFamilyName();
- textFontWeight = typeface.getWeight();
- }
- mSystemFontFamilyName = systemFontFamilyName;
- mTextFontWeight = textFontWeight;
- mTextStyle = textView.getTypefaceStyle();
- mAllCaps = textView.isAllCaps();
- mShadowRadius = textView.getShadowRadius();
- mShadowDx = textView.getShadowDx();
- mShadowDy = textView.getShadowDy();
- mElegantTextHeight = textView.isElegantTextHeight();
- mFallbackLineSpacing = textView.isFallbackLineSpacing();
- mLetterSpacing = textView.getLetterSpacing();
- mFontFeatureSettings = textView.getFontFeatureSettings();
- mFontVariationSettings = textView.getFontVariationSettings();
- mLineBreakStyle = textView.getLineBreakStyle();
- mLineBreakWordStyle = textView.getLineBreakWordStyle();
- mTextScaleX = textView.getTextScaleX();
- mTextColorHighlight = textView.getHighlightColor();
- mTextColor = textView.getCurrentTextColor();
- mTextColorHint = textView.getCurrentHintTextColor();
- mTextColorLink = textView.getLinkTextColors();
- int maxLength = -1;
- for (InputFilter filter: textView.getFilters()) {
- if (filter instanceof InputFilter.LengthFilter) {
- maxLength = ((InputFilter.LengthFilter) filter).getMax();
- // There is at most one LengthFilter.
- break;
- }
- }
- mMaxLength = maxLength;
+ private TextAppearanceInfo(@NonNull final TextAppearanceInfo.Builder builder) {
+ mTextSize = builder.mTextSize;
+ mTextLocales = builder.mTextLocales;
+ mSystemFontFamilyName = builder.mSystemFontFamilyName;
+ mTextFontWeight = builder.mTextFontWeight;
+ mTextStyle = builder.mTextStyle;
+ mAllCaps = builder.mAllCaps;
+ mShadowDx = builder.mShadowDx;
+ mShadowDy = builder.mShadowDy;
+ mShadowRadius = builder.mShadowRadius;
+ mShadowColor = builder.mShadowColor;
+ mElegantTextHeight = builder.mElegantTextHeight;
+ mFallbackLineSpacing = builder.mFallbackLineSpacing;
+ mLetterSpacing = builder.mLetterSpacing;
+ mFontFeatureSettings = builder.mFontFeatureSettings;
+ mFontVariationSettings = builder.mFontVariationSettings;
+ mLineBreakStyle = builder.mLineBreakStyle;
+ mLineBreakWordStyle = builder.mLineBreakWordStyle;
+ mTextScaleX = builder.mTextScaleX;
+ mHighlightTextColor = builder.mHighlightTextColor;
+ mTextColor = builder.mTextColor;
+ mHintTextColor = builder.mHintTextColor;
+ mLinkTextColor = builder.mLinkTextColor;
}
@Override
@@ -214,6 +198,7 @@
dest.writeFloat(mShadowDx);
dest.writeFloat(mShadowDy);
dest.writeFloat(mShadowRadius);
+ dest.writeInt(mShadowColor);
dest.writeBoolean(mElegantTextHeight);
dest.writeBoolean(mFallbackLineSpacing);
dest.writeFloat(mLetterSpacing);
@@ -222,14 +207,13 @@
dest.writeInt(mLineBreakStyle);
dest.writeInt(mLineBreakWordStyle);
dest.writeFloat(mTextScaleX);
- dest.writeInt(mTextColorHighlight);
+ dest.writeInt(mHighlightTextColor);
dest.writeInt(mTextColor);
- dest.writeInt(mTextColorHint);
- dest.writeTypedObject(mTextColorLink, flags);
- dest.writeInt(mMaxLength);
+ dest.writeInt(mHintTextColor);
+ dest.writeInt(mLinkTextColor);
}
- private TextAppearanceInfo(@NonNull Parcel in) {
+ TextAppearanceInfo(@NonNull Parcel in) {
mTextSize = in.readFloat();
mTextLocales = LocaleList.CREATOR.createFromParcel(in);
mAllCaps = in.readBoolean();
@@ -239,6 +223,7 @@
mShadowDx = in.readFloat();
mShadowDy = in.readFloat();
mShadowRadius = in.readFloat();
+ mShadowColor = in.readInt();
mElegantTextHeight = in.readBoolean();
mFallbackLineSpacing = in.readBoolean();
mLetterSpacing = in.readFloat();
@@ -247,11 +232,10 @@
mLineBreakStyle = in.readInt();
mLineBreakWordStyle = in.readInt();
mTextScaleX = in.readFloat();
- mTextColorHighlight = in.readInt();
+ mHighlightTextColor = in.readInt();
mTextColor = in.readInt();
- mTextColorHint = in.readInt();
- mTextColorLink = in.readTypedObject(ColorStateList.CREATOR);
- mMaxLength = in.readInt();
+ mHintTextColor = in.readInt();
+ mLinkTextColor = in.readInt();
}
@NonNull
@@ -268,14 +252,14 @@
};
/**
- * Returns the text size (in pixels) for current {@link TextView}.
+ * Returns the text size (in pixels) for current editor.
*/
public @Px float getTextSize() {
return mTextSize;
}
/**
- * Returns the LocaleList of the text.
+ * Returns the {@link LocaleList} of the text.
*/
@NonNull
public LocaleList getTextLocales() {
@@ -286,31 +270,38 @@
* Returns the font family name if the {@link Typeface} of the text is created from a
* system font family. Returns null if no {@link Typeface} is specified, or it is not created
* from a system font family.
+ *
+ * @see Typeface#getSystemFontFamilyName()
*/
@Nullable
- public String getFontFamilyName() {
+ public String getSystemFontFamilyName() {
return mSystemFontFamilyName;
}
/**
- * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified.
+ * Returns the weight of the text, or {@code FontStyle#FONT_WEIGHT_UNSPECIFIED}
+ * when no {@link Typeface} is specified.
*/
- public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() {
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ public int getTextFontWeight() {
return mTextFontWeight;
}
/**
* Returns the style (normal, bold, italic, bold|italic) of the text. Returns
- * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for
- * more information.
+ * {@link Typeface#NORMAL} when no {@link Typeface} is specified.
+ *
+ * @see Typeface
*/
public @Typeface.Style int getTextStyle() {
return mTextStyle;
}
/**
- * Returns whether the transformation method applied to the current {@link TextView} is set to
- * ALL CAPS.
+ * Returns whether the transformation method applied to the current editor is set to all caps.
+ *
+ * @see TextView#setAllCaps(boolean)
+ * @see TextView#setTransformationMethod(TransformationMethod)
*/
public boolean isAllCaps() {
return mAllCaps;
@@ -318,6 +309,8 @@
/**
* Returns the horizontal offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowDx() {
return mShadowDx;
@@ -325,6 +318,8 @@
/**
* Returns the vertical offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowDy() {
return mShadowDy;
@@ -332,15 +327,28 @@
/**
* Returns the blur radius (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowRadius() {
return mShadowRadius;
}
/**
+ * Returns the color of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ public @ColorInt int getShadowColor() {
+ return mShadowColor;
+ }
+
+ /**
* Returns {@code true} if the elegant height metrics flag is set. This setting selects font
* variants that have not been compacted to fit Latin-based vertical metrics, and also increases
* top and bottom bounds to provide more space.
+ *
+ * @see Paint#isElegantTextHeight()
*/
public boolean isElegantTextHeight() {
return mElegantTextHeight;
@@ -411,13 +419,17 @@
/**
* Returns the color of the text selection highlight.
+ *
+ * @see TextView#getHighlightColor()
*/
- public @ColorInt int getTextColorHighlight() {
- return mTextColorHighlight;
+ public @ColorInt int getHighlightTextColor() {
+ return mHighlightTextColor;
}
/**
- * Returns the current text color.
+ * Returns the current text color of the editor.
+ *
+ * @see TextView#getCurrentTextColor()
*/
public @ColorInt int getTextColor() {
return mTextColor;
@@ -425,27 +437,22 @@
/**
* Returns the current color of the hint text.
+ *
+ * @see TextView#getCurrentHintTextColor()
*/
- public @ColorInt int getTextColorHint() {
- return mTextColorHint;
+ public @ColorInt int getHintTextColor() {
+ return mHintTextColor;
}
/**
- * Returns the text color for links.
+ * Returns the text color used to paint the links in the editor.
+ *
+ * @see TextView#getLinkTextColors()
*/
- @Nullable
- public ColorStateList getTextColorLink() {
- return mTextColorLink;
+ public @ColorInt int getLinkTextColor() {
+ return mLinkTextColor;
}
- /**
- * Returns the max length of text, which is used to set an input filter to constrain the text
- * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter}
- * in the Editor.
- */
- public int getMaxLength() {
- return mMaxLength;
- }
@Override
public boolean equals(Object o) {
@@ -456,27 +463,29 @@
&& mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle
&& mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0
&& Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare(
- that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength
+ that.mShadowRadius, mShadowRadius) == 0 && that.mShadowColor == mShadowColor
&& mElegantTextHeight == that.mElegantTextHeight
&& mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare(
that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle
&& mLineBreakWordStyle == that.mLineBreakWordStyle
- && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor
- && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor()
- && mTextColorHint == that.mTextColorHint && Objects.equals(
- mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName,
- that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings,
- that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings,
- that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
+ && mHighlightTextColor == that.mHighlightTextColor
+ && mTextColor == that.mTextColor
+ && mLinkTextColor == that.mLinkTextColor
+ && mHintTextColor == that.mHintTextColor
+ && Objects.equals(mTextLocales, that.mTextLocales)
+ && Objects.equals(mSystemFontFamilyName, that.mSystemFontFamilyName)
+ && Objects.equals(mFontFeatureSettings, that.mFontFeatureSettings)
+ && Objects.equals(mFontVariationSettings, that.mFontVariationSettings)
+ && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
}
@Override
public int hashCode() {
return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight,
- mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight,
- mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings,
- mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor,
- mTextColorHint, mTextColorLink, mMaxLength);
+ mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mShadowColor,
+ mElegantTextHeight, mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings,
+ mFontVariationSettings, mLineBreakStyle, mLineBreakWordStyle, mTextScaleX,
+ mHighlightTextColor, mTextColor, mHintTextColor, mLinkTextColor);
}
@Override
@@ -491,6 +500,7 @@
+ ", mShadowDx=" + mShadowDx
+ ", mShadowDy=" + mShadowDy
+ ", mShadowRadius=" + mShadowRadius
+ + ", mShadowColor=" + mShadowColor
+ ", mElegantTextHeight=" + mElegantTextHeight
+ ", mFallbackLineSpacing=" + mFallbackLineSpacing
+ ", mLetterSpacing=" + mLetterSpacing
@@ -499,11 +509,290 @@
+ ", mLineBreakStyle=" + mLineBreakStyle
+ ", mLineBreakWordStyle=" + mLineBreakWordStyle
+ ", mTextScaleX=" + mTextScaleX
- + ", mTextColorHighlight=" + mTextColorHighlight
+ + ", mHighlightTextColor=" + mHighlightTextColor
+ ", mTextColor=" + mTextColor
- + ", mTextColorHint=" + mTextColorHint
- + ", mTextColorLink=" + mTextColorLink
- + ", mMaxLength=" + mMaxLength
+ + ", mHintTextColor=" + mHintTextColor
+ + ", mLinkTextColor=" + mLinkTextColor
+ '}';
}
+
+ /**
+ * Builder for {@link TextAppearanceInfo}.
+ */
+ public static final class Builder {
+ private @Px float mTextSize = -1;
+ private @NonNull LocaleList mTextLocales = LocaleList.getAdjustedDefault();
+ @Nullable private String mSystemFontFamilyName = null;
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ private int mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+ private @Typeface.Style int mTextStyle = NORMAL;
+ private boolean mAllCaps = false;
+ private @Px float mShadowDx = 0;
+ private @Px float mShadowDy = 0;
+ private @Px float mShadowRadius = 0;
+ private @ColorInt int mShadowColor = 0;
+ private boolean mElegantTextHeight = false;
+ private boolean mFallbackLineSpacing = false;
+ private float mLetterSpacing = 0;
+ @Nullable private String mFontFeatureSettings = null;
+ @Nullable private String mFontVariationSettings = null;
+ @LineBreakConfig.LineBreakStyle
+ private int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+ @LineBreakConfig.LineBreakWordStyle
+ private int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+ private float mTextScaleX = 1;
+ private @ColorInt int mHighlightTextColor = 0;
+ private @ColorInt int mTextColor = 0;
+ private @ColorInt int mHintTextColor = 0;
+ private @ColorInt int mLinkTextColor = 0;
+
+ /**
+ * Set the text size (in pixels) obtained from the current editor.
+ */
+ @NonNull
+ public Builder setTextSize(@Px float textSize) {
+ mTextSize = textSize;
+ return this;
+ }
+
+ /**
+ * Set the {@link LocaleList} of the text.
+ */
+ @NonNull
+ public Builder setTextLocales(@NonNull LocaleList textLocales) {
+ mTextLocales = textLocales;
+ return this;
+ }
+
+ /**
+ * Set the system font family name if the {@link Typeface} of the text is created from a
+ * system font family.
+ *
+ * @see Typeface#getSystemFontFamilyName()
+ */
+ @NonNull
+ public Builder setSystemFontFamilyName(@Nullable String systemFontFamilyName) {
+ mSystemFontFamilyName = systemFontFamilyName;
+ return this;
+ }
+
+ /**
+ * Set the weight of the text.
+ */
+ @NonNull
+ public Builder setTextFontWeight(
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED,
+ to = FontStyle.FONT_WEIGHT_MAX) int textFontWeight) {
+ mTextFontWeight = textFontWeight;
+ return this;
+ }
+
+ /**
+ * Set the style (normal, bold, italic, bold|italic) of the text.
+ *
+ * @see Typeface
+ */
+ @NonNull
+ public Builder setTextStyle(@Typeface.Style int textStyle) {
+ mTextStyle = textStyle;
+ return this;
+ }
+
+ /**
+ * Set whether the transformation method applied to the current editor is set to all caps.
+ *
+ * @see TextView#setAllCaps(boolean)
+ * @see TextView#setTransformationMethod(TransformationMethod)
+ */
+ @NonNull
+ public Builder setAllCaps(boolean allCaps) {
+ mAllCaps = allCaps;
+ return this;
+ }
+
+ /**
+ * Set the horizontal offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowDx(@Px float shadowDx) {
+ mShadowDx = shadowDx;
+ return this;
+ }
+
+ /**
+ * Set the vertical offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowDy(@Px float shadowDy) {
+ mShadowDy = shadowDy;
+ return this;
+ }
+
+ /**
+ * Set the blur radius (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowRadius(@Px float shadowRadius) {
+ mShadowRadius = shadowRadius;
+ return this;
+ }
+
+ /**
+ * Set the color of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowColor(@ColorInt int shadowColor) {
+ mShadowColor = shadowColor;
+ return this;
+ }
+
+ /**
+ * Set the elegant height metrics flag. This setting selects font variants that
+ * have not been compacted to fit Latin-based vertical metrics, and also increases
+ * top and bottom bounds to provide more space.
+ *
+ * @see Paint#isElegantTextHeight()
+ */
+ @NonNull
+ public Builder setElegantTextHeight(boolean elegantTextHeight) {
+ mElegantTextHeight = elegantTextHeight;
+ return this;
+ }
+
+ /**
+ * Set whether to expand linespacing based on fallback fonts.
+ *
+ * @see TextView#setFallbackLineSpacing(boolean)
+ */
+ @NonNull
+ public Builder setFallbackLineSpacing(boolean fallbackLineSpacing) {
+ mFallbackLineSpacing = fallbackLineSpacing;
+ return this;
+ }
+
+ /**
+ * Set the text letter-spacing, which determines the spacing between characters.
+ * The value is in 'EM' units. Normally, this value is 0.0.
+ */
+ @NonNull
+ public Builder setLetterSpacing(float letterSpacing) {
+ mLetterSpacing = letterSpacing;
+ return this;
+ }
+
+ /**
+ * Set the font feature settings.
+ *
+ * @see Paint#getFontFeatureSettings()
+ */
+ @NonNull
+ public Builder setFontFeatureSettings(@Nullable String fontFeatureSettings) {
+ mFontFeatureSettings = fontFeatureSettings;
+ return this;
+ }
+
+ /**
+ * Set the font variation settings. Returns null if no variation is specified.
+ *
+ * @see Paint#getFontVariationSettings()
+ */
+ @NonNull
+ public Builder setFontVariationSettings(@Nullable String fontVariationSettings) {
+ mFontVariationSettings = fontVariationSettings;
+ return this;
+ }
+
+ /**
+ * Set the line-break strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakStyle(int)
+ */
+ @NonNull
+ public Builder setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
+ mLineBreakStyle = lineBreakStyle;
+ return this;
+ }
+
+ /**
+ * Set the line-break word strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakWordStyle(int)
+ */
+ @NonNull
+ public Builder setLineBreakWordStyle(
+ @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+ mLineBreakWordStyle = lineBreakWordStyle;
+ return this;
+ }
+
+ /**
+ * Set the extent by which text should be stretched horizontally.
+ */
+ @NonNull
+ public Builder setTextScaleX(float textScaleX) {
+ mTextScaleX = textScaleX;
+ return this;
+ }
+
+ /**
+ * Set the color of the text selection highlight.
+ *
+ * @see TextView#getHighlightColor()
+ */
+ @NonNull
+ public Builder setHighlightTextColor(@ColorInt int highlightTextColor) {
+ mHighlightTextColor = highlightTextColor;
+ return this;
+ }
+
+ /**
+ * Set the current text color of the editor.
+ *
+ * @see TextView#getCurrentTextColor()
+ */
+ @NonNull
+ public Builder setTextColor(@ColorInt int textColor) {
+ mTextColor = textColor;
+ return this;
+ }
+
+ /**
+ * Set the current color of the hint text.
+ *
+ * @see TextView#getCurrentHintTextColor()
+ */
+ @NonNull
+ public Builder setHintTextColor(@ColorInt int hintTextColor) {
+ mHintTextColor = hintTextColor;
+ return this;
+ }
+
+ /**
+ * Set the text color used to paint the links in the editor.
+ *
+ * @see TextView#getLinkTextColors()
+ */
+ @NonNull
+ public Builder setLinkTextColor(@ColorInt int linkTextColor) {
+ mLinkTextColor = linkTextColor;
+ return this;
+ }
+
+ /**
+ * Returns {@link TextAppearanceInfo} using parameters in this
+ * {@link TextAppearanceInfo.Builder}.
+ */
+ @NonNull
+ public TextAppearanceInfo build() {
+ return new TextAppearanceInfo(this);
+ }
+ }
}
diff --git a/core/java/android/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java
index 5474557..89cb6b2 100644
--- a/core/java/android/webkit/ConsoleMessage.java
+++ b/core/java/android/webkit/ConsoleMessage.java
@@ -68,4 +68,4 @@
public int lineNumber() {
return mLineNumber;
}
-};
+}
diff --git a/core/java/android/webkit/ValueCallback.java b/core/java/android/webkit/ValueCallback.java
index 5c7d97f..3d5bb49 100644
--- a/core/java/android/webkit/ValueCallback.java
+++ b/core/java/android/webkit/ValueCallback.java
@@ -25,4 +25,4 @@
* @param value The value.
*/
public void onReceiveValue(T value);
-};
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 56524a2..5740f86 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,6 +21,7 @@
import android.R;
import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +38,7 @@
import android.content.UndoOwner;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -49,8 +51,10 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RenderNode;
+import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.fonts.FontStyle;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
@@ -4762,8 +4766,41 @@
}
if (includeTextAppearance) {
- TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView);
- builder.setTextAppearanceInfo(textAppearanceInfo);
+ Typeface typeface = mTextView.getPaint().getTypeface();
+ String systemFontFamilyName = null;
+ int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+ if (typeface != null) {
+ systemFontFamilyName = typeface.getSystemFontFamilyName();
+ textFontWeight = typeface.getWeight();
+ }
+ ColorStateList linkTextColors = mTextView.getLinkTextColors();
+ @ColorInt int linkTextColor = linkTextColors != null
+ ? linkTextColors.getDefaultColor() : 0;
+
+ TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder();
+ appearanceBuilder.setTextSize(mTextView.getTextSize())
+ .setTextLocales(mTextView.getTextLocales())
+ .setSystemFontFamilyName(systemFontFamilyName)
+ .setTextFontWeight(textFontWeight)
+ .setTextStyle(mTextView.getTypefaceStyle())
+ .setAllCaps(mTextView.isAllCaps())
+ .setShadowDx(mTextView.getShadowDx())
+ .setShadowDy(mTextView.getShadowDy())
+ .setShadowRadius(mTextView.getShadowRadius())
+ .setShadowColor(mTextView.getShadowColor())
+ .setElegantTextHeight(mTextView.isElegantTextHeight())
+ .setFallbackLineSpacing(mTextView.isFallbackLineSpacing())
+ .setLetterSpacing(mTextView.getLetterSpacing())
+ .setFontFeatureSettings(mTextView.getFontFeatureSettings())
+ .setFontVariationSettings(mTextView.getFontVariationSettings())
+ .setLineBreakStyle(mTextView.getLineBreakStyle())
+ .setLineBreakWordStyle(mTextView.getLineBreakWordStyle())
+ .setTextScaleX(mTextView.getTextScaleX())
+ .setHighlightTextColor(mTextView.getHighlightColor())
+ .setTextColor(mTextView.getCurrentTextColor())
+ .setHintTextColor(mTextView.getCurrentHintTextColor())
+ .setLinkTextColor(linkTextColor);
+ builder.setTextAppearanceInfo(appearanceBuilder.build());
}
imm.updateCursorAnchorInfo(mTextView, builder.build());
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1144b59..bf1a2bd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -982,8 +982,11 @@
/**
* The last input source on this TextView.
+ *
+ * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a
+ * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc.
*/
- private int mLastInputSource = InputDevice.SOURCE_UNKNOWN;
+ private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN;
/**
* The TextView does not auto-size text (default).
@@ -2286,11 +2289,13 @@
* @param familyName family name string, e.g. "serif"
* @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
* @param style a typeface style
- * @param weight a weight value for the Typeface or -1 if not specified.
+ * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
+ * if not specified.
*/
private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
@XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
- @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ int weight) {
if (typeface == null && familyName != null) {
// Lookup normal Typeface from system font map.
final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
@@ -2317,7 +2322,8 @@
}
private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
- @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ int weight) {
if (weight >= 0) {
weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
final boolean italic = (style & Typeface.ITALIC) != 0;
@@ -4018,7 +4024,7 @@
boolean mFontFamilyExplicit = false;
int mTypefaceIndex = -1;
int mTextStyle = 0;
- int mFontWeight = -1;
+ int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
boolean mAllCaps = false;
int mShadowColor = 0;
float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
@@ -6943,18 +6949,18 @@
if (isPassword) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
- Typeface.NORMAL, -1 /* weight, not specifeid */);
+ Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
} else if (isVisiblePassword) {
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
- Typeface.NORMAL, -1 /* weight, not specified */);
+ Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
} else if (wasPassword || wasVisiblePassword) {
// not in password mode, clean up typeface and transformation
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
- -1 /* weight, not specified */);
+ FontStyle.FONT_WEIGHT_UNSPECIFIED);
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 9b91cf2..a25e035 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -89,8 +89,6 @@
@Nullable
private final IOnBackInvokedCallback mOnBackInvokedCallback;
private final boolean mPrepareRemoteAnimation;
- @Nullable
- private WindowContainerToken mDepartingWindowContainerToken;
/**
* Create a new {@link BackNavigationInfo} instance.
@@ -100,20 +98,15 @@
* back preview.
* @param onBackInvokedCallback The back callback registered by the current top level window.
* @param departingWindowContainerToken The {@link WindowContainerToken} of departing window.
- * @param isPrepareRemoteAnimation Return whether the core is preparing a back gesture
- * animation, if true, the caller of startBackNavigation should
- * be expected to receive an animation start callback.
*/
private BackNavigationInfo(@BackTargetType int type,
@Nullable RemoteCallback onBackNavigationDone,
@Nullable IOnBackInvokedCallback onBackInvokedCallback,
- boolean isPrepareRemoteAnimation,
- @Nullable WindowContainerToken departingWindowContainerToken) {
+ boolean isPrepareRemoteAnimation) {
mType = type;
mOnBackNavigationDone = onBackNavigationDone;
mOnBackInvokedCallback = onBackInvokedCallback;
mPrepareRemoteAnimation = isPrepareRemoteAnimation;
- mDepartingWindowContainerToken = departingWindowContainerToken;
}
private BackNavigationInfo(@NonNull Parcel in) {
@@ -121,7 +114,6 @@
mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
mPrepareRemoteAnimation = in.readBoolean();
- mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR);
}
@Override
@@ -130,7 +122,6 @@
dest.writeTypedObject(mOnBackNavigationDone, flags);
dest.writeStrongInterface(mOnBackInvokedCallback);
dest.writeBoolean(mPrepareRemoteAnimation);
- dest.writeTypedObject(mDepartingWindowContainerToken, flags);
}
/**
@@ -164,18 +155,6 @@
}
/**
- * Returns the {@link WindowContainerToken} of the highest container in the hierarchy being
- * removed.
- * <p>
- * For example, if an Activity is the last one of its Task, the Task's token will be given.
- * Otherwise, it will be the Activity's token.
- */
- @Nullable
- public WindowContainerToken getDepartingWindowContainerToken() {
- return mDepartingWindowContainerToken;
- }
-
- /**
* Callback to be called when the back preview is finished in order to notify the server that
* it can clean up the resources created for the animation.
*
@@ -212,7 +191,6 @@
+ "mType=" + typeToString(mType) + " (" + mType + ")"
+ ", mOnBackNavigationDone=" + mOnBackNavigationDone
+ ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
- + ", mWindowContainerToken=" + mDepartingWindowContainerToken
+ '}';
}
@@ -248,8 +226,6 @@
@Nullable
private IOnBackInvokedCallback mOnBackInvokedCallback = null;
private boolean mPrepareRemoteAnimation;
- @Nullable
- private WindowContainerToken mDepartingWindowContainerToken = null;
/**
* @see BackNavigationInfo#getType()
@@ -285,20 +261,12 @@
}
/**
- * @see BackNavigationInfo#getDepartingWindowContainerToken()
- */
- public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) {
- mDepartingWindowContainerToken = windowContainerToken;
- }
-
- /**
* Builds and returns an instance of {@link BackNavigationInfo}
*/
public BackNavigationInfo build() {
return new BackNavigationInfo(mType, mOnBackNavigationDone,
mOnBackInvokedCallback,
- mPrepareRemoteAnimation,
- mDepartingWindowContainerToken);
+ mPrepareRemoteAnimation);
}
}
}
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index f274d1a..0ce076b6 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -49,7 +49,7 @@
public boolean isParentFrameClippedByDisplayCutout;
- public float sizeCompatScale = 1f;
+ public float compatScale = 1f;
public ClientWindowFrames() {
}
@@ -62,7 +62,7 @@
attachedFrame = new Rect(other.attachedFrame);
}
isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout;
- sizeCompatScale = other.sizeCompatScale;
+ compatScale = other.compatScale;
}
private ClientWindowFrames(Parcel in) {
@@ -76,7 +76,7 @@
parentFrame.readFromParcel(in);
attachedFrame = in.readTypedObject(Rect.CREATOR);
isParentFrameClippedByDisplayCutout = in.readBoolean();
- sizeCompatScale = in.readFloat();
+ compatScale = in.readFloat();
}
@Override
@@ -86,7 +86,7 @@
parentFrame.writeToParcel(dest, flags);
dest.writeTypedObject(attachedFrame, flags);
dest.writeBoolean(isParentFrameClippedByDisplayCutout);
- dest.writeFloat(sizeCompatScale);
+ dest.writeFloat(compatScale);
}
@Override
@@ -97,7 +97,7 @@
+ " parentFrame=" + parentFrame.toShortString(sb)
+ (attachedFrame != null ? " attachedFrame=" + attachedFrame.toShortString() : "")
+ (isParentFrameClippedByDisplayCutout ? " parentClippedByDisplayCutout" : "")
- + (sizeCompatScale != 1f ? " sizeCompatScale=" + sizeCompatScale : "") + "}";
+ + (compatScale != 1f ? " sizeCompatScale=" + compatScale : "") + "}";
}
@Override
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index a5aefd5..f55932e 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -16,6 +16,8 @@
package android.window;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -142,6 +144,14 @@
*/
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {}
+ /**
+ * This is called when an Activity is entering PIP.
+ * Returns {@code true} if the Activity is allowed to enter PIP.
+ */
+ public boolean isEnteringPipAllowed(int uid) {
+ return isWindowingModeSupported(WINDOWING_MODE_PINNED);
+ }
+
/** Dump debug data */
public void dump(String prefix, final PrintWriter pw) {
pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}");
diff --git a/core/java/android/window/IBackAnimationFinishedCallback.aidl b/core/java/android/window/IBackAnimationFinishedCallback.aidl
index 8afc003..f034339 100644
--- a/core/java/android/window/IBackAnimationFinishedCallback.aidl
+++ b/core/java/android/window/IBackAnimationFinishedCallback.aidl
@@ -22,6 +22,6 @@
* @param trigger Whether the back gesture has passed the triggering threshold.
* {@hide}
*/
-oneway interface IBackAnimationFinishedCallback {
+interface IBackAnimationFinishedCallback {
void onAnimationFinished(in boolean triggerBack);
}
\ No newline at end of file
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 3250dd8..d25c8a8 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,13 +39,13 @@
* animations if the transition only contains windows that belong to the organized
* TaskFragments in the given Task.
*/
- void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId,
+ void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
in RemoteAnimationDefinition definition);
/**
* Unregisters remote animations per transition type for the organizer.
*/
- void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
+ void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
/**
* Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 4248096..3950739 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -57,12 +57,13 @@
* option is provided.
*
* The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onReadyToSync} callback that contains a
- * {@link SyncBufferCallback}.
- * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}.
- * This makes sure the SurfaceSyncGroup knows when the SyncTarget is complete, allowing the
- * SurfaceSyncGroup to get the Transaction that contains the buffer.
- * 3. When the final SyncBufferCallback finishes for the SurfaceSyncGroup, in most cases the
+ * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
+ * {@link TransactionReadyCallback}.
+ * 2. Each {@link SyncTarget} needs to invoke
+ * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
+ * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
+ * Transaction that contains the buffer.
+ * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
* transaction is applied and then the sync complete callbacks are invoked, letting the callers know
* the sync is now complete.
*
@@ -86,8 +87,6 @@
private final Transaction mTransaction = sTransactionFactory.get();
@GuardedBy("mLock")
private boolean mSyncReady;
- @GuardedBy("mLock")
- private final Set<SyncTarget> mSyncTargets = new ArraySet<>();
@GuardedBy("mLock")
private Consumer<Transaction> mSyncRequestCompleteCallback;
@@ -197,14 +196,13 @@
* Add a {@link SyncTarget} to a sync set. The sync set will wait for all
* SyncableSurfaces to complete before notifying.
*
- * @param syncTarget A SyncableSurface that implements how to handle syncing
- * buffers.
+ * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
* @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
*/
public boolean addToSync(SyncTarget syncTarget) {
- SyncBufferCallback syncBufferCallback = new SyncBufferCallback() {
+ TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
@Override
- public void onBufferReady(Transaction t) {
+ public void onTransactionReady(Transaction t) {
synchronized (mLock) {
if (t != null) {
mTransaction.merge(t);
@@ -221,10 +219,9 @@
+ "SyncTargets can be added.");
return false;
}
- mPendingSyncs.add(syncBufferCallback.hashCode());
- mSyncTargets.add(syncTarget);
+ mPendingSyncs.add(transactionReadyCallback.hashCode());
}
- syncTarget.onReadyToSync(syncBufferCallback);
+ syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
return true;
}
@@ -256,17 +253,13 @@
Log.d(TAG, "Successfully finished sync id=" + this);
}
- for (SyncTarget syncTarget : mSyncTargets) {
- syncTarget.onSyncComplete();
- }
- mSyncTargets.clear();
mSyncRequestCompleteCallback.accept(mTransaction);
mFinished = true;
}
/**
* Add a Transaction to this sync set. This allows the caller to provide other info that
- * should be synced with the buffers.
+ * should be synced with the transactions.
*/
public void addTransactionToSync(Transaction t) {
synchronized (mLock) {
@@ -334,9 +327,10 @@
}
@Override
- public void onReadyToSync(SyncBufferCallback syncBufferCallback) {
+ public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ TransactionReadyCallback transactionReadyCallback) {
mFrameCallbackConsumer.accept(
- () -> mSurfaceView.syncNextFrame(syncBufferCallback::onBufferReady));
+ () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
}
}
@@ -345,22 +339,19 @@
*/
public interface SyncTarget {
/**
- * Called when the Syncable is ready to begin handing a sync request. When invoked, the
- * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)}
- * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable
- * to be marked as complete.
+ * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
+ * sync request. When invoked, the implementor is required to call
+ * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
+ * SurfaceSyncGroup to fully complete.
*
* Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
*
- * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady
+ * @param parentSyncGroup The sync group this target has been added to.
+ * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
+ * onTransactionReady
*/
- void onReadyToSync(SyncBufferCallback syncBufferCallback);
-
- /**
- * There's no guarantee about the thread this callback is invoked on.
- */
- default void onSyncComplete() {
- }
+ void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ TransactionReadyCallback transactionReadyCallback);
}
/**
@@ -368,14 +359,14 @@
* completed. The caller should invoke the calls when the rendering has started and finished a
* frame.
*/
- public interface SyncBufferCallback {
+ public interface TransactionReadyCallback {
/**
- * Invoked when the transaction contains the buffer and is ready to sync.
+ * Invoked when the transaction is ready to sync.
*
- * @param t The transaction that contains the buffer to be synced. This can be null if
- * there's nothing to sync
+ * @param t The transaction that contains the anything to be included in the synced. This
+ * can be null if there's nothing to sync
*/
- void onBufferReady(@Nullable Transaction t);
+ void onTransactionReady(@Nullable Transaction t);
}
/**
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index e2c8a31..dc60edd 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -83,6 +83,12 @@
private final boolean mIsTaskFragmentClearedForPip;
/**
+ * Whether the last running activity of the TaskFragment was removed because it was reordered to
+ * front of the Task.
+ */
+ private final boolean mIsClearedForReorderActivityToFront;
+
+ /**
* The maximum {@link ActivityInfo.WindowLayout#minWidth} and
* {@link ActivityInfo.WindowLayout#minHeight} aggregated from the TaskFragment's child
* activities.
@@ -96,7 +102,7 @@
@NonNull Configuration configuration, int runningActivityCount,
boolean isVisible, @NonNull List<IBinder> activities, @NonNull Point positionInParent,
boolean isTaskClearedForReuse, boolean isTaskFragmentClearedForPip,
- @NonNull Point minimumDimensions) {
+ boolean isClearedForReorderActivityToFront, @NonNull Point minimumDimensions) {
mFragmentToken = requireNonNull(fragmentToken);
mToken = requireNonNull(token);
mConfiguration.setTo(configuration);
@@ -106,6 +112,7 @@
mPositionInParent.set(positionInParent);
mIsTaskClearedForReuse = isTaskClearedForReuse;
mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip;
+ mIsClearedForReorderActivityToFront = isClearedForReorderActivityToFront;
mMinimumDimensions.set(minimumDimensions);
}
@@ -160,6 +167,11 @@
return mIsTaskFragmentClearedForPip;
}
+ /** @hide */
+ public boolean isClearedForReorderActivityToFront() {
+ return mIsClearedForReorderActivityToFront;
+ }
+
@WindowingMode
public int getWindowingMode() {
return mConfiguration.windowConfiguration.getWindowingMode();
@@ -207,6 +219,7 @@
&& mPositionInParent.equals(that.mPositionInParent)
&& mIsTaskClearedForReuse == that.mIsTaskClearedForReuse
&& mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip
+ && mIsClearedForReorderActivityToFront == that.mIsClearedForReorderActivityToFront
&& mMinimumDimensions.equals(that.mMinimumDimensions);
}
@@ -220,6 +233,7 @@
mPositionInParent.readFromParcel(in);
mIsTaskClearedForReuse = in.readBoolean();
mIsTaskFragmentClearedForPip = in.readBoolean();
+ mIsClearedForReorderActivityToFront = in.readBoolean();
mMinimumDimensions.readFromParcel(in);
}
@@ -235,6 +249,7 @@
mPositionInParent.writeToParcel(dest, flags);
dest.writeBoolean(mIsTaskClearedForReuse);
dest.writeBoolean(mIsTaskFragmentClearedForPip);
+ dest.writeBoolean(mIsClearedForReorderActivityToFront);
mMinimumDimensions.writeToParcel(dest, flags);
}
@@ -262,8 +277,9 @@
+ " activities=" + mActivities
+ " positionInParent=" + mPositionInParent
+ " isTaskClearedForReuse=" + mIsTaskClearedForReuse
- + " isTaskFragmentClearedForPip" + mIsTaskFragmentClearedForPip
- + " minimumDimensions" + mMinimumDimensions
+ + " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip
+ + " mIsClearedForReorderActivityToFront=" + mIsClearedForReorderActivityToFront
+ + " minimumDimensions=" + mMinimumDimensions
+ "}";
}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 8df6541..ef19c55 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -122,16 +122,13 @@
/**
* Registers remote animations per transition type for the organizer. It will override the
* animations if the transition only contains windows that belong to the organized
- * TaskFragments in the given Task.
- *
- * @param taskId overrides if the transition only contains windows belonging to this Task.
+ * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
* @hide
*/
@CallSuper
- public void registerRemoteAnimations(int taskId,
- @NonNull RemoteAnimationDefinition definition) {
+ public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
try {
- getController().registerRemoteAnimations(mInterface, taskId, definition);
+ getController().registerRemoteAnimations(mInterface, definition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -142,9 +139,9 @@
* @hide
*/
@CallSuper
- public void unregisterRemoteAnimations(int taskId) {
+ public void unregisterRemoteAnimations() {
try {
- getController().unregisterRemoteAnimations(mInterface, taskId);
+ getController().unregisterRemoteAnimations(mInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index db15145..e62d5c9 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -296,7 +296,7 @@
out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
}
}
- out.append("]").toString();
+ out.append("]");
out.append(" flags=" + TransitionInfo.flagsToString(mFlags));
out.append(" mustBeTask=" + mMustBeTask);
out.append(" order=" + containerOrderToString(mOrder));
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 0956a71..c2da638 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -138,8 +138,11 @@
/** The container is a system window, excluding wallpaper and input-method. */
public static final int FLAG_IS_SYSTEM_WINDOW = 1 << 16;
+ /** The window was animated by back gesture. */
+ public static final int FLAG_BACK_GESTURE_ANIMATED = 1 << 17;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 17;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 18;
/** The change belongs to a window that won't contain activities. */
public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -165,6 +168,7 @@
FLAG_IS_BEHIND_STARTING_WINDOW,
FLAG_IS_OCCLUDED,
FLAG_IS_SYSTEM_WINDOW,
+ FLAG_BACK_GESTURE_ANIMATED,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -380,6 +384,9 @@
if ((flags & FLAG_IS_SYSTEM_WINDOW) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_SYSTEM_WINDOW");
}
+ if ((flags & FLAG_BACK_GESTURE_ANIMATED) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("FLAG_BACK_GESTURE_ANIMATED");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2d29c59..fc64eb9 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -454,6 +454,23 @@
}
/**
+ * Sets whether a container is being drag-resized.
+ * When {@code true}, the client will reuse a single (larger) surface size to avoid
+ * continuous allocations on every size change.
+ *
+ * @param container WindowContainerToken of the task that changed its drag resizing state
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
+ boolean dragResizing) {
+ final Change change = getOrCreateChange(container.asBinder());
+ change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
+ change.mDragResizing = dragResizing;
+ return this;
+ }
+
+ /**
* Sends a pending intent in sync.
* @param sender The PendingIntent sender.
* @param intent The fillIn intent to patch over the sender's base intent.
@@ -711,6 +728,29 @@
}
/**
+ * Sets the TaskFragment {@code container} to have a companion TaskFragment {@code companion}.
+ * This indicates that the organizer will remove the TaskFragment when the companion
+ * TaskFragment is removed.
+ *
+ * @param container the TaskFragment container
+ * @param companion the companion TaskFragment. If it is {@code null}, the transaction will
+ * reset the companion TaskFragment.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setCompanionTaskFragment(@NonNull IBinder container,
+ @Nullable IBinder companion) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT)
+ .setContainer(container)
+ .setReparentContainer(companion)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets/removes the always on top flag for this {@code windowContainer}. See
* {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
* Please note that this method is only intended to be used for a
@@ -894,12 +934,14 @@
public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
+ public static final int CHANGE_DRAG_RESIZING = 1 << 8;
private final Configuration mConfiguration = new Configuration();
private boolean mFocusable = true;
private boolean mHidden = false;
private boolean mIgnoreOrientationRequest = false;
private boolean mForceTranslucent = false;
+ private boolean mDragResizing = false;
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
@@ -920,6 +962,7 @@
mHidden = in.readBoolean();
mIgnoreOrientationRequest = in.readBoolean();
mForceTranslucent = in.readBoolean();
+ mDragResizing = in.readBoolean();
mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
@@ -968,6 +1011,9 @@
if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
mForceTranslucent = other.mForceTranslucent;
}
+ if ((other.mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+ mDragResizing = other.mDragResizing;
+ }
mChangeMask |= other.mChangeMask;
if (other.mActivityWindowingMode >= 0) {
mActivityWindowingMode = other.mActivityWindowingMode;
@@ -1027,6 +1073,15 @@
return mForceTranslucent;
}
+ /** Gets the requested drag resizing state. */
+ public boolean getDragResizing() {
+ if ((mChangeMask & CHANGE_DRAG_RESIZING) == 0) {
+ throw new RuntimeException("Drag resizing not set. "
+ + "Check CHANGE_DRAG_RESIZING first");
+ }
+ return mDragResizing;
+ }
+
public int getChangeMask() {
return mChangeMask;
}
@@ -1088,6 +1143,9 @@
if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
sb.append("focusable:" + mFocusable + ",");
}
+ if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+ sb.append("dragResizing:" + mDragResizing + ",");
+ }
if (mBoundsChangeTransaction != null) {
sb.append("hasBoundsTransaction,");
}
@@ -1105,6 +1163,7 @@
dest.writeBoolean(mHidden);
dest.writeBoolean(mIgnoreOrientationRequest);
dest.writeBoolean(mForceTranslucent);
+ dest.writeBoolean(mDragResizing);
dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
@@ -1169,6 +1228,7 @@
public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
+ public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1383,6 +1443,11 @@
}
@NonNull
+ public IBinder getCompanionContainer() {
+ return mReparent;
+ }
+
+ @NonNull
public IBinder getCallingActivity() {
return mReparent;
}
@@ -1492,6 +1557,9 @@
return "{RemoveTask: task=" + mContainer + "}";
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
return "{finishActivity: activity=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+ return "{setCompanionTaskFragment: container = " + mContainer + " companion = "
+ + mReparent + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
diff --git a/core/java/android/window/WindowProvider.java b/core/java/android/window/WindowProvider.java
index b078b93..dbdc68f 100644
--- a/core/java/android/window/WindowProvider.java
+++ b/core/java/android/window/WindowProvider.java
@@ -15,8 +15,10 @@
*/
package android.window;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
+import android.os.IBinder;
import android.view.WindowManager.LayoutParams.WindowType;
/**
@@ -36,4 +38,11 @@
/** Gets the launch options of this provider */
@Nullable
Bundle getWindowContextOptions();
+
+ /**
+ * Gets the WindowContextToken of this provider.
+ * @see android.content.Context#getWindowContextToken
+ */
+ @NonNull
+ IBinder getWindowContextToken();
}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 2d2c8de..fdc3e5a 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -27,7 +27,10 @@
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.app.Service;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
import android.content.Context;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
@@ -54,6 +57,8 @@
private final WindowContextController mController = new WindowContextController(mWindowToken);
private WindowManager mWindowManager;
private boolean mInitialized;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
/**
* Returns {@code true} if the {@code windowContextOptions} declares that it is a
@@ -118,6 +123,48 @@
return mOptions;
}
+ @SuppressLint({"OnNameExpected", "ExecutorRegistration"})
+ // Suppress lint because this is a legacy named function and doesn't have an optional param
+ // for executor.
+ // TODO(b/259347943): Update documentation for U.
+ /**
+ * Here we override to prevent WindowProviderService from invoking
+ * {@link Application.registerComponentCallback}, which will result in callback registered
+ * for process-level Configuration change updates.
+ */
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ // For broadcasting Configuration Changes.
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void onConfigurationChanged(@Nullable Configuration configuration) {
+ // This is only called from WindowTokenClient.
+ mCallbacksController.dispatchConfigurationChanged(configuration);
+ }
+
+ /**
+ * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+ * low memory and trim memory events.
+ */
+ @Override
+ public void onLowMemory() {
+ mCallbacksController.dispatchLowMemory();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mCallbacksController.dispatchTrimMemory(level);
+ }
+
/**
* Returns the display ID to launch this {@link WindowProviderService}.
*
@@ -181,5 +228,6 @@
public void onDestroy() {
super.onDestroy();
mController.detachIfNeeded();
+ mCallbacksController.clearCallbacks();
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index c8eec7d..d37779b 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2096,7 +2096,8 @@
return matchingShortcuts;
}
- private void sendShortcutManagerShareTargetResults(
+ @VisibleForTesting
+ protected void sendShortcutManagerShareTargetResults(
int shortcutType, ServiceResultInfo[] results) {
final Message msg = Message.obtain();
msg.what = ChooserHandler.SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS;
@@ -3873,7 +3874,11 @@
}
}
- static class ServiceResultInfo {
+ /**
+ * Shortcuts grouped by application.
+ */
+ @VisibleForTesting
+ public static class ServiceResultInfo {
public final DisplayResolveInfo originalTarget;
public final List<ChooserTarget> resultTargets;
public final UserHandle userHandle;
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 30da4b4..88447da 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -58,11 +58,12 @@
SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation);
- SyncNotedAppOp startProxyOperation(int code, in AttributionSource attributionSource,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags,
- int proxiedAttributionFlags, int attributionChainId);
- void finishProxyOperation(int code, in AttributionSource attributionSource,
+ SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+ in AttributionSource attributionSource, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
+ int attributionChainId);
+ void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource,
boolean skipProxyOperation);
// Remaining methods are only used in Java.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index e926605..a237e98 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -492,6 +492,12 @@
/* workProfileUserHandle= */ null);
}
+ private UserHandle getIntentUser() {
+ return getIntent().hasExtra(EXTRA_CALLING_USER)
+ ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
+ : getUser();
+ }
+
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
Intent[] initialIntents,
List<ResolveInfo> rList,
@@ -500,9 +506,7 @@
// the intent resolver is started in the other profile. Since this is the only case when
// this happens, we check for it here and set the current profile's tab.
int selectedProfile = getCurrentProfile();
- UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER)
- ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
- : getUser();
+ UserHandle intentUser = getIntentUser();
if (!getUser().equals(intentUser)) {
if (getPersonalProfileUserHandle().equals(intentUser)) {
selectedProfile = PROFILE_PERSONAL;
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 97f4b0f..a21a842 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -59,6 +59,7 @@
/**
* The state of the source process of an association.
*/
+ @SuppressWarnings("ParcelableCreator")
public static final class SourceState implements Parcelable {
private @NonNull final ProcessStats mProcessStats;
private @Nullable final AssociationState mAssociationState;
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 0f64f6d..292a50b 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -584,6 +584,13 @@
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
/**
+ * (boolean) Whether to ignore the source package for determining whether to use remote copy
+ * behavior in the clipboard UI.
+ */
+ public static final String CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE =
+ "clipboard_ignore_remote_copy_source";
+
+ /**
* (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE
*/
public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS
new file mode 100644
index 0000000..ee865b1
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index c62fba9..1e3714e 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -21,6 +21,7 @@
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
@@ -69,9 +70,11 @@
void setSessionEnabled(IInputMethodSession session, boolean enabled);
- void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver);
+ void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken,
+ int flags, in ResultReceiver resultReceiver);
- void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
+ void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken,
+ int flags, in ResultReceiver resultReceiver);
void updateEditorToolType(int toolType);
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 4babb70..f77e962 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -17,6 +17,7 @@
package com.android.internal.inputmethod;
import android.net.Uri;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.infra.AndroidFuture;
@@ -41,7 +42,8 @@
void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
void notifyUserActionAsync();
- void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible);
+ void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+ in @nullable ImeTracker.Token statsToken);
void onStylusHandwritingReady(int requestId, int pid);
void resetStylusHandwriting(int requestId);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 67c2103..66e3333 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -25,6 +25,7 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.GuardedBy;
@@ -100,7 +101,7 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int}.
+ * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int)}.
*
* @param vis visibility flags
* @param backDisposition disposition flags
@@ -250,7 +251,7 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)}
+ * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
*
* @param flags additional operating flags
* @param reason the reason to hide soft input
@@ -316,7 +317,7 @@
/**
* Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean,
- * IBooleanResultCallback)}
+ * AndroidFuture)}
*
* @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same
* IME
@@ -375,22 +376,25 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean)}.
+ * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean,
+ * ImeTracker.Token)}.
*
* @param showOrHideInputToken placeholder token that maps to window requesting
* {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or
- * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow
- * (IBinder, int)}
+ * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder,
+ * int)}
* @param setVisible {@code true} to set IME visible, else hidden.
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
@AnyThread
- public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible) {
+ public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+ @Nullable ImeTracker.Token statsToken) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
return;
}
try {
- ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible);
+ ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
} 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 b0d5922..614f962 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -27,6 +27,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
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_QUICK_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
@@ -90,7 +91,6 @@
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
-import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -226,6 +226,7 @@
public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
public static final int CUJ_RECENTS_SCROLLING = 65;
+ public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
private static final int NO_STATSD_LOGGING = -1;
@@ -300,6 +301,7 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
};
private static class InstanceHolder {
@@ -389,7 +391,8 @@
CUJ_SHADE_CLEAR_ALL,
CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
CUJ_LOCKSCREEN_OCCLUSION,
- CUJ_RECENTS_SCROLLING
+ CUJ_RECENTS_SCROLLING,
+ CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -413,7 +416,6 @@
public InteractionJankMonitor(@NonNull HandlerThread worker) {
// Check permission early.
DeviceConfig.enforceReadPermission(
- ActivityThread.currentApplication().getApplicationContext(),
DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
mRunningTrackers = new SparseArray<>();
@@ -906,6 +908,8 @@
return "LOCKSCREEN_OCCLUSION";
case CUJ_RECENTS_SCROLLING:
return "RECENTS_SCROLLING";
+ case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
+ return "LAUNCHER_APP_SWIPE_TO_RECENTS";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 696f0ff..556e146 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -403,7 +403,7 @@
* Returns true if this instance only supports reading history.
*/
public boolean isReadOnly() {
- return mActiveFile == null;
+ return mActiveFile == null || mHistoryDir == null;
}
/**
@@ -1292,7 +1292,9 @@
&& mHistoryLastWritten.batteryHealth == cur.batteryHealth
&& mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
&& mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
- && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+ && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
+ && mHistoryLastWritten.measuredEnergyDetails == null
+ && mHistoryLastWritten.cpuUsageDetails == null) {
// We can merge this new change in with the last one. Merging is
// allowed as long as only the states have changed, and within those states
// as long as no bit has changed both between now and the last entry, as
@@ -1761,8 +1763,8 @@
* Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
*/
public void writeHistory() {
- if (mActiveFile == null) {
- Slog.w(TAG, "writeHistory: no history file associated with this instance");
+ if (isReadOnly()) {
+ Slog.w(TAG, "writeHistory: this instance instance is read-only");
return;
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 0a29fc52..eb62cb0 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -735,7 +735,7 @@
}
protected boolean shouldRecordDetailedData() {
- return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+ return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
}
/**
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index e9d55db..1276fb9 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -236,7 +236,7 @@
}
protected boolean shouldKeepSample() {
- return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+ return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
}
/** Updates the sampling interval. */
diff --git a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
index 9be686a..a1ad5d5 100644
--- a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -26,5 +26,7 @@
interface IBinaryTransparencyService {
String getSignedImageInfo();
- Map getApexInfo();
+ List getApexInfo();
+
+ List getMeasurementsForAllPackages();
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 2805dcc..0645eb7 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -290,7 +290,7 @@
}
protected boolean shouldCollectDetailedData() {
- return ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ return ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
}
private static class DispatchSession {
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index 2143bc1..9ddb8c7 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import android.util.IntArray;
+
import com.android.internal.util.ProcFileReader;
import java.io.FileInputStream;
@@ -35,6 +37,7 @@
public class ProcLocksReader {
private final String mPath;
private ProcFileReader mReader = null;
+ private IntArray mPids = new IntArray();
public ProcLocksReader() {
mPath = "/proc/locks";
@@ -51,9 +54,13 @@
public interface ProcLocksReaderCallback {
/**
* Call the callback function of handleBlockingFileLocks().
- * @param pid Each process that hold file locks blocking other processes.
+ * @param pids Each process that hold file locks blocking other processes.
+ * pids[0] is the process blocking others
+ * pids[1..n-1] are the processes being blocked
+ * NOTE: pids are cleared immediately after onBlockingFileLock() returns. If the caller
+ * needs to cache it, please make a copy, e.g. by calling pids.toArray().
*/
- void onBlockingFileLock(int pid);
+ void onBlockingFileLock(IntArray pids);
}
/**
@@ -64,8 +71,7 @@
public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
long last = -1;
long id; // ordinal position of the lock in the list
- int owner = -1; // the PID of the process that owns the lock
- int pid = -1; // the PID of the process blocking others
+ int pid = -1; // the PID of the process being blocked
if (mReader == null) {
mReader = new ProcFileReader(new FileInputStream(mPath));
@@ -73,26 +79,49 @@
mReader.rewind();
}
+ mPids.clear();
while (mReader.hasMoreData()) {
id = mReader.nextLong(true); // lock id
if (id == last) {
- mReader.finishLine(); // blocked lock
- if (pid < 0) {
- pid = owner; // get pid from the previous line
- callback.onBlockingFileLock(pid);
+ // blocked lock found
+ mReader.nextIgnored(); // ->
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ mPids.add(pid);
}
- continue;
+
+ mReader.finishLine();
} else {
- pid = -1; // a new lock
+ // process blocking lock and move on to a new lock
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
+ mPids.clear();
+ }
+
+ // new lock found
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ if (mPids.size() == 0) {
+ mPids.add(pid);
+ } else {
+ mPids.set(0, pid);
+ }
+ }
+ mReader.finishLine();
+ last = id;
}
-
- mReader.nextIgnored(); // lock type: POSIX?
- mReader.nextIgnored(); // lock type: MANDATORY?
- mReader.nextIgnored(); // lock type: RW?
-
- owner = mReader.nextInt(); // pid
- mReader.finishLine();
- last = id;
+ }
+ // The last unprocessed blocking lock immediately before EOF
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
}
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6..8a9445d 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -16,10 +16,14 @@
package com.android.internal.os;
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
+
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.type.DefaultMimeMapFactory;
import android.net.TrafficStats;
@@ -36,6 +40,7 @@
import dalvik.system.RuntimeHooks;
import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
import libcore.content.type.MimeMap;
@@ -260,10 +265,31 @@
*/
TrafficStats.attachSocketTagger();
+ /*
+ * Initialize the zip path validator callback depending on the targetSdk.
+ */
+ initZipPathValidatorCallback();
+
initialized = true;
}
/**
+ * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+ * entry names.
+ * Otherwise: clear the callback to the default validation.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void initZipPathValidatorCallback() {
+ if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+ ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+ } else {
+ ZipPathValidator.clearCallback();
+ }
+ }
+
+ /**
* Returns an HTTP user agent of the form
* "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
*/
diff --git a/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
new file mode 100644
index 0000000..a6ee108
--- /dev/null
+++ b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+
+import dalvik.system.ZipPathValidator;
+
+import java.io.File;
+import java.util.zip.ZipException;
+
+/**
+ * A child implementation of the {@link dalvik.system.ZipPathValidator.Callback} that removes the
+ * risk of zip path traversal vulnerabilities.
+ *
+ * @hide
+ */
+public class SafeZipPathValidatorCallback implements ZipPathValidator.Callback {
+ /**
+ * This change targets zip path traversal vulnerabilities by throwing
+ * {@link java.util.zip.ZipException} if zip path entries contain ".." or start with "/".
+ * <p>
+ * The exception will be thrown in {@link java.util.zip.ZipInputStream#getNextEntry} or
+ * {@link java.util.zip.ZipFile#ZipFile(String)}.
+ * <p>
+ * This validation is enabled for apps with targetSDK >= U.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL = 242716250L;
+
+ @Override
+ public void onZipEntryAccess(@NonNull String path) throws ZipException {
+ if (path.startsWith("/")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ if (path.contains("..")) {
+ // If the string does contain "..", break it down into its actual name elements to
+ // ensure it actually contains ".." as a name, not just a name like "foo..bar" or even
+ // "foo..", which should be fine.
+ File file = new File(path);
+ while (file != null) {
+ if (file.getName().equals("..")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ file = file.getParentFile();
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index be3f172..680f8fe 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.content.Intent;
import android.os.SystemClock;
import com.android.internal.os.anr.AnrLatencyTracker;
@@ -92,7 +93,17 @@
/** Record for a broadcast receiver timeout. */
@NonNull
- public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) {
+ String reason = "Broadcast of " + intent.toString();
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+ long timeoutDurationMs) {
+ String reason = "Broadcast of " + intent.toString() + ", waited " + timeoutDurationMs
+ + "ms";
return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 3e988e6..145aeaf 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2402,7 +2402,7 @@
return;
}
final ThreadedRenderer renderer = getThreadedRenderer();
- if (renderer != null) {
+ if (renderer != null && !CAPTION_ON_SHELL) {
loadBackgroundDrawablesIfNeeded();
WindowInsets rootInsets = getRootWindowInsets();
mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 7f45c09..3ab11a8 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.security;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Build;
import android.os.SystemProperties;
import android.system.Os;
@@ -41,6 +42,7 @@
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -77,17 +79,23 @@
return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION;
}
- /** Enables fs-verity for the file with a PKCS#7 detached signature file. */
- public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath)
+ /** Enables fs-verity for the file with an optional PKCS#7 detached signature file. */
+ public static void setUpFsverity(@NonNull String filePath, @Nullable String signaturePath)
throws IOException {
- if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
- throw new SecurityException("Signature file is unexpectedly large: " + signaturePath);
+ byte[] rawSignature = null;
+ if (signaturePath != null) {
+ Path path = Paths.get(signaturePath);
+ if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+ throw new SecurityException("Signature file is unexpectedly large: "
+ + signaturePath);
+ }
+ rawSignature = Files.readAllBytes(path);
}
- setUpFsverity(filePath, Files.readAllBytes(Paths.get(signaturePath)));
+ setUpFsverity(filePath, rawSignature);
}
- /** Enables fs-verity for the file with a PKCS#7 detached signature bytes. */
- public static void setUpFsverity(@NonNull String filePath, @NonNull byte[] pkcs7Signature)
+ /** Enables fs-verity for the file with an optional PKCS#7 detached signature bytes. */
+ public static void setUpFsverity(@NonNull String filePath, @Nullable byte[] pkcs7Signature)
throws IOException {
// This will fail if the public key is not already in .fs-verity kernel keyring.
int errno = enableFsverityNative(filePath, pkcs7Signature);
@@ -227,7 +235,7 @@
}
private static native int enableFsverityNative(@NonNull String filePath,
- @NonNull byte[] pkcs7Signature);
+ @Nullable byte[] pkcs7Signature);
private static native int measureFsverityNative(@NonNull String filePath,
@NonNull byte[] digest);
private static native int statxForFsverityNative(@NonNull String filePath);
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 8fcb6d5..4b7b91c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -468,7 +468,7 @@
boolean shouldSample;
int traceThreshold;
synchronized (mLock) {
- shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ shouldSample = ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
traceThreshold = mTraceThresholdPerAction[action];
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index fe77236..2ac4309 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,6 +16,7 @@
package com.android.internal.view;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.input.InputManager;
import android.os.Bundle;
@@ -31,6 +32,7 @@
import android.view.PointerIcon;
import android.view.ScrollCaptureResponse;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -66,11 +68,13 @@
}
@Override
- public void showInsets(@InsetsType int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
@Override
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index 32ce0fe..1ae1307 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -241,4 +241,4 @@
mSurfaceFrame.right = width;
mSurfaceFrame.bottom = height;
}
-};
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 40c6a05..00bc3f2 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,6 +17,7 @@
package com.android.internal.view;
import android.os.ResultReceiver;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
@@ -54,11 +55,12 @@
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
InputMethodSubtype getLastInputMethodSubtype(int userId);
- boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
- int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason);
- boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
+ boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+ in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
in @nullable ResultReceiver resultReceiver, int reason);
-
+ boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+ in @nullable ImeTracker.Token statsToken, int flags,
+ in @nullable ResultReceiver resultReceiver, int reason);
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
// @NonNull
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 5b08bb1..6063c90 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -54,6 +54,12 @@
// TODO(b/183140900) split store escrow key errors into detailed ones.
/**
+ * This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+ * It does any synthetic password related work that was delayed from earlier in the boot.
+ */
+ public abstract void onThirdPartyAppsStarted();
+
+ /**
* Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
* doesn't have an LSKF.
* <p>
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d9ca16e..0798110 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -19,6 +19,7 @@
#define LOG_NDEBUG 1
#include <android-base/macros.h>
+#include <android-base/parsebool.h>
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
#include <android_runtime/AndroidRuntime.h>
@@ -52,6 +53,8 @@
using namespace android;
using android::base::GetBoolProperty;
using android::base::GetProperty;
+using android::base::ParseBool;
+using android::base::ParseBoolResult;
extern int register_android_os_Binder(JNIEnv* env);
extern int register_android_os_Process(JNIEnv* env);
@@ -703,17 +706,24 @@
// Read if we are using the profile configuration, do this at the start since the last ART args
// take precedence.
- property_get("dalvik.vm.profilebootclasspath", propBuf, "");
- std::string profile_boot_class_path_flag = propBuf;
- // Empty means the property is unset and we should default to the phenotype property.
- // The possible values are {"true", "false", ""}
- if (profile_boot_class_path_flag.empty()) {
- profile_boot_class_path_flag = server_configurable_flags::GetServerConfigurableFlag(
- RUNTIME_NATIVE_BOOT_NAMESPACE,
- PROFILE_BOOT_CLASS_PATH,
- /*default_value=*/ "");
+ std::string profile_boot_class_path_flag =
+ server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
+ PROFILE_BOOT_CLASS_PATH,
+ /*default_value=*/"");
+ bool profile_boot_class_path;
+ switch (ParseBool(profile_boot_class_path_flag)) {
+ case ParseBoolResult::kError:
+ // Default to the system property.
+ profile_boot_class_path =
+ GetBoolProperty("dalvik.vm.profilebootclasspath", /*default_value=*/false);
+ break;
+ case ParseBoolResult::kTrue:
+ profile_boot_class_path = true;
+ break;
+ case ParseBoolResult::kFalse:
+ profile_boot_class_path = false;
+ break;
}
- const bool profile_boot_class_path = (profile_boot_class_path_flag == "true");
if (profile_boot_class_path) {
addOption("-Xcompiler-option");
addOption("--count-hotness-in-compiled-code");
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index a068008..9f39c32 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -99,3 +99,5 @@
# PM
per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
+# Stats/expresslog
+per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index d05a24f..ac1401d 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -40,6 +40,7 @@
typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_closeSession)(APerformanceHintSession* session);
+typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
bool gAPerformanceHintBindingInitialized = false;
APH_getManager gAPH_getManagerFn = nullptr;
@@ -48,6 +49,7 @@
APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
APH_closeSession gAPH_closeSessionFn = nullptr;
+APH_sendHint gAPH_sendHintFn = nullptr;
void ensureAPerformanceHintBindingInitialized() {
if (gAPerformanceHintBindingInitialized) return;
@@ -88,6 +90,11 @@
LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
"Failed to find required symbol APerformanceHint_closeSession!");
+ gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
+ LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
+ "Failed to find required symbol "
+ "APerformanceHint_sendHint!");
+
gAPerformanceHintBindingInitialized = true;
}
@@ -138,6 +145,11 @@
gAPH_closeSessionFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr));
}
+static void nativeSendHint(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, jint hint) {
+ ensureAPerformanceHintBindingInitialized();
+ gAPH_sendHintFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), hint);
+}
+
static const JNINativeMethod gPerformanceHintMethods[] = {
{"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
{"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -145,6 +157,7 @@
{"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
{"nativeReportActualWorkDuration", "(JJ)V", (void*)nativeReportActualWorkDuration},
{"nativeCloseSession", "(J)V", (void*)nativeCloseSession},
+ {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
};
int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 571a8e2..b6bf617 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -126,6 +126,7 @@
addHyphenator("nn", 2, 2); // Norwegian Nynorsk
addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
+ addHyphenator("pl", 2, 2); // Polish
addHyphenator("pt", 2, 3); // Portuguese
addHyphenator("ru", 2, 2); // Russian
addHyphenator("sk", 2, 2); // Slovak
@@ -141,7 +142,6 @@
// Following two hyphenators do not have pattern files but there is some special logic based on
// language.
addHyphenatorWithoutPatternFile("ca", 2, 2); // Catalan
- addHyphenatorWithoutPatternFile("pl", 2, 2); // Polish
// English locales that fall back to en-US. The data is from CLDR. It's all English locales,
// minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>).
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 9f88f33..01837f4 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -25,6 +25,7 @@
#include <inttypes.h>
#include <mutex>
#include <stdio.h>
+#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -880,7 +881,7 @@
case FAILED_TRANSACTION: {
ALOGE("!!! FAILED BINDER TRANSACTION !!! (parcel size = %d)", parcelSize);
const char* exceptionToThrow;
- char msg[128];
+ std::string msg;
// TransactionTooLargeException is a checked exception, only throw from certain methods.
// TODO(b/28321379): Transaction size is the most common cause for FAILED_TRANSACTION
// but it is not the only one. The Binder driver can return BR_FAILED_REPLY
@@ -890,7 +891,7 @@
if (canThrowRemoteException && parcelSize > 200*1024) {
// bona fide large payload
exceptionToThrow = "android/os/TransactionTooLargeException";
- snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
+ msg = base::StringPrintf("data parcel size %d bytes", parcelSize);
} else {
// Heuristic: a payload smaller than this threshold "shouldn't" be too
// big, so it's probably some other, more subtle problem. In practice
@@ -899,11 +900,10 @@
exceptionToThrow = (canThrowRemoteException)
? "android/os/DeadObjectException"
: "java/lang/RuntimeException";
- snprintf(msg, sizeof(msg) - 1,
- "Transaction failed on small parcel; remote process probably died, but "
- "this could also be caused by running out of binder buffer space");
+ msg = "Transaction failed on small parcel; remote process probably died, but "
+ "this could also be caused by running out of binder buffer space";
}
- jniThrowException(env, exceptionToThrow, msg);
+ jniThrowException(env, exceptionToThrow, msg.c_str());
} break;
case FDS_NOT_ALLOWED:
jniThrowException(env, "java/lang/RuntimeException",
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 2b932cb..98814bf 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -380,8 +380,8 @@
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());
}
- MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
- if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
+ const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*inputEvent);
+ if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
*outConsumedBatch = true;
}
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index a30935b..80df0ea 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -82,7 +82,7 @@
reinterpret_cast<jlong>(event));
}
-jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) {
+jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event) {
jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
gMotionEventClassInfo.obtain);
if (env->ExceptionCheck() || !eventObj) {
@@ -98,7 +98,7 @@
android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
}
- destEvent->copyFrom(event, true);
+ destEvent->copyFrom(&event, true);
return eventObj;
}
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
index 9ce4bf3..32a280e 100644
--- a/core/jni/android_view_MotionEvent.h
+++ b/core/jni/android_view_MotionEvent.h
@@ -26,7 +26,7 @@
/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance.
* Returns NULL on error. */
-extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event);
+extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event);
/* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
* Returns NULL if the event is NULL or if it is uninitialized. */
diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index 8305bd0..dabee69 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -48,10 +48,6 @@
if (rfd.get() < 0) {
return errno;
}
- ScopedByteArrayRO signature_bytes(env, signature);
- if (signature_bytes.get() == nullptr) {
- return EINVAL;
- }
fsverity_enable_arg arg = {};
arg.version = 1;
@@ -59,8 +55,18 @@
arg.block_size = 4096;
arg.salt_size = 0;
arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
- arg.sig_size = signature_bytes.size();
- arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
+
+ if (signature != nullptr) {
+ ScopedByteArrayRO signature_bytes(env, signature);
+ if (signature_bytes.get() == nullptr) {
+ return EINVAL;
+ }
+ arg.sig_size = signature_bytes.size();
+ arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
+ } else {
+ arg.sig_size = 0;
+ arg.sig_ptr = reinterpret_cast<uintptr_t>(nullptr);
+ }
if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
return errno;
diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto
index 5fdcfdf..7037a6c 100644
--- a/core/proto/android/app/location_time_zone_manager.proto
+++ b/core/proto/android/app/location_time_zone_manager.proto
@@ -40,7 +40,7 @@
message LocationTimeZoneManagerServiceStateProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
- optional GeolocationTimeZoneSuggestionProto last_suggestion = 1;
+ optional LocationTimeZoneProviderEventProto last_event = 1;
repeated TimeZoneProviderStateProto primary_provider_states = 2;
repeated TimeZoneProviderStateProto secondary_provider_states = 3;
repeated ControllerStateEnum controller_states = 4;
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b52aa82..cd4a36f 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -22,13 +22,38 @@
option java_multiple_files = true;
option java_outer_classname = "TimeZoneDetectorProto";
-// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone
+// Represents a LocationTimeZoneProviderEvent that can be / has been passed to the time zone
// detector.
+message LocationTimeZoneProviderEventProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional GeolocationTimeZoneSuggestionProto suggestion = 1;
+ repeated string debug_info = 2;
+ optional LocationTimeZoneAlgorithmStatusProto algorithm_status = 3;
+}
+
+// Represents a LocationTimeZoneAlgorithmStatus that can be / has been passed to the time zone
+// detector.
+message LocationTimeZoneAlgorithmStatusProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional DetectionAlgorithmStatusEnum status = 1;
+}
+
+// The state enum for detection algorithms.
+enum DetectionAlgorithmStatusEnum {
+ DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+ DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+ DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+}
+
+// Represents a GeolocationTimeZoneSuggestion that can be contained in a
+// LocationTimeZoneProviderEvent.
message GeolocationTimeZoneSuggestionProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
repeated string zone_ids = 1;
- repeated string debug_info = 2;
}
/*
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index bd4f990..004d5d6 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -189,6 +189,8 @@
optional bool is_enhanced_discharge_prediction_personalized = 54;
optional bool is_low_power_standby_active = 55;
optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
+ // The battery level drained by the dream.
+ optional int32 battery_level_drained_while_dreaming = 57;
}
// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f0fbff1..ec43ae3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1153,8 +1153,16 @@
<!-- Allows an application to read image or video files from external storage that a user has
selected via the permission prompt photo picker. Apps can check this permission to verify that
a user has decided to use the photo picker, instead of granting access to
- {@link #READ_MEDIA_IMAGES or #READ_MEDIA_VIDEO}. It does not prevent apps from accessing the
- standard photo picker manually.
+ {@link #READ_MEDIA_IMAGES} or {@link #READ_MEDIA_VIDEO}. It does not prevent apps from
+ accessing the standard photo picker manually. This permission should be requested alongside
+ {@link #READ_MEDIA_IMAGES} and/or {@link #READ_MEDIA_VIDEO}, depending on which type of media
+ is desired.
+ <p> This permission will be automatically added to an app's manifest if the app requests
+ {@link #READ_MEDIA_IMAGES}, {@link #READ_MEDIA_VIDEO}, or {@link #ACCESS_MEDIA_LOCATION}
+ regardless of target SDK. If an app does not request this permission, then the grant dialog
+ will return `PERMISSION_GRANTED` for {@link #READ_MEDIA_IMAGES} and/or
+ {@link #READ_MEDIA_VIDEO}, but the app will only have access to the media selected by the
+ user. This false grant state will persist until the app goes into the background.
<p>Protection level: dangerous -->
<permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"
android:permissionGroup="android.permission-group.UNDEFINED"
@@ -2533,6 +2541,19 @@
android:description="@string/permdesc_modifyAudioSettings"
android:protectionLevel="normal" />
+ <!-- ==================================================== -->
+ <!-- Permissions related to screen capture -->
+ <!-- ==================================================== -->
+ <eat-comment />
+
+ <!-- Allows an application to get notified when a screen capture of its windows is attempted.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.DETECT_SCREEN_CAPTURE"
+ android:label="@string/permlab_detectScreenCapture"
+ android:description="@string/permdesc_detectScreenCapture"
+ android:protectionLevel="normal" />
+
<!-- ======================================== -->
<!-- Permissions for factory reset protection -->
<!-- ======================================== -->
@@ -3164,10 +3185,10 @@
<permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>
- <!-- Allows an application to hint that a broadcast is associated with an
- "interactive" usage scenario
+ <!-- Allows an application to hint that a component lifecycle operation such as sending
+ a broadcast is associated with an "interactive" usage scenario.
@hide -->
- <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
+ <permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Must be required by activities that handle the intent action
@@ -6171,6 +6192,116 @@
android:label="@string/permlab_foregroundService"
android:protectionLevel="normal|instant" />
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "camera".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"
+ android:description="@string/permdesc_foregroundServiceCamera"
+ android:label="@string/permlab_foregroundServiceCamera"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "connectedDevice".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
+ android:description="@string/permdesc_foregroundServiceConnectedDevice"
+ android:label="@string/permlab_foregroundServiceConnectedDevice"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "dataSync".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
+ android:description="@string/permdesc_foregroundServiceDataSync"
+ android:label="@string/permlab_foregroundServiceDataSync"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "location".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
+ android:description="@string/permdesc_foregroundServiceLocation"
+ android:label="@string/permlab_foregroundServiceLocation"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaPlayback".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
+ android:description="@string/permdesc_foregroundServiceMediaPlayback"
+ android:label="@string/permlab_foregroundServiceMediaPlayback"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaProjection".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
+ android:description="@string/permdesc_foregroundServiceMediaProjection"
+ android:label="@string/permlab_foregroundServiceMediaProjection"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "microphone".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
+ android:description="@string/permdesc_foregroundServiceMicrophone"
+ android:label="@string/permlab_foregroundServiceMicrophone"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "phoneCall".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"
+ android:description="@string/permdesc_foregroundServicePhoneCall"
+ android:label="@string/permlab_foregroundServicePhoneCall"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "health".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH"
+ android:description="@string/permdesc_foregroundServiceHealth"
+ android:label="@string/permlab_foregroundServiceHealth"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "remoteMessaging".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING"
+ android:description="@string/permdesc_foregroundServiceRemoteMessaging"
+ android:label="@string/permlab_foregroundServiceRemoteMessaging"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "systemExempted".
+ Apps are allowed to use this type only in the use cases listed in
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"
+ android:description="@string/permdesc_foregroundServiceSystemExempted"
+ android:label="@string/permlab_foregroundServiceSystemExempted"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "specialUse".
+ <p>Protection level: signature|appop|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
+ android:description="@string/permdesc_foregroundServiceSpecialUse"
+ android:label="@string/permlab_foregroundServiceSpecialUse"
+ android:protectionLevel="signature|appop|instant" />
+
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
@@ -6908,13 +7039,6 @@
android:exported="false">
</activity>
- <activity android:name="com.android.internal.app.LogAccessDialogActivity"
- android:theme="@style/Theme.Translucent.NoTitleBar"
- android:process=":ui"
- android:excludeFromRecents="true"
- android:exported="false">
- </activity>
-
<activity android:name="com.android.server.notification.NASLearnMoreActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 22f40a1..6d05e07 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -1,5 +1,4 @@
adamp@google.com
-alanv@google.com
asc@google.com
cinek@google.com
dsandler@android.com
@@ -20,9 +19,6 @@
ogunwale@google.com
patb@google.com
shanh@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-toddke@google.com
tsuji@google.com
yamasani@google.com
diff --git a/core/res/res/anim/dock_bottom_enter.xml b/core/res/res/anim/dock_bottom_enter.xml
deleted file mode 100644
index bfb97b6..0000000
--- a/core/res/res/anim/dock_bottom_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromYDelta="100%" android:toYDelta="0"
- android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit.xml b/core/res/res/anim/dock_bottom_exit.xml
deleted file mode 100644
index 4e15448..0000000
--- a/core/res/res/anim/dock_bottom_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromYDelta="0" android:toYDelta="100%"
- android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit_keyguard.xml b/core/res/res/anim/dock_bottom_exit_keyguard.xml
deleted file mode 100644
index 4de3ce5..0000000
--- a/core/res/res/anim/dock_bottom_exit_keyguard.xml
+++ /dev/null
@@ -1,22 +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
- -->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting while on Keyguard -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/fast_out_linear_in">
- <translate android:fromYDelta="0" android:toYDelta="100%"
- android:duration="200"/>
-</set>
\ No newline at end of file
diff --git a/core/res/res/anim/dock_left_enter.xml b/core/res/res/anim/dock_left_enter.xml
deleted file mode 100644
index 7f5dfd5..0000000
--- a/core/res/res/anim/dock_left_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the left of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromXDelta="-100%" android:toXDelta="0"
- android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_left_exit.xml b/core/res/res/anim/dock_left_exit.xml
deleted file mode 100644
index 11cbc0b3..0000000
--- a/core/res/res/anim/dock_left_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromXDelta="0" android:toXDelta="-100%"
- android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_enter.xml b/core/res/res/anim/dock_right_enter.xml
deleted file mode 100644
index a92c7d2..0000000
--- a/core/res/res/anim/dock_right_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromXDelta="100%" android:toXDelta="0"
- android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_exit.xml b/core/res/res/anim/dock_right_exit.xml
deleted file mode 100644
index 80e4dc3..0000000
--- a/core/res/res/anim/dock_right_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromXDelta="0" android:toXDelta="100%"
- android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_top_enter.xml b/core/res/res/anim/dock_top_enter.xml
deleted file mode 100644
index f763fb5..0000000
--- a/core/res/res/anim/dock_top_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromYDelta="-100%" android:toYDelta="0"
- android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_top_exit.xml b/core/res/res/anim/dock_top_exit.xml
deleted file mode 100644
index 995b7d0..0000000
--- a/core/res/res/anim/dock_top_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromYDelta="0" android:toYDelta="-100%"
- android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index b20bfef..3ad6dc3 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID se verstek is nie beperk nie. Volgende oproep: nie beperk nie"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Diens nie verskaf nie."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Jy kan nie die beller-ID-instelling verander nie."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Het data oorgeskakel na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Jy kan dit enige tyd in instellings verander"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Geen mobiele datadiens nie"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Noodoproepe is onbeskikbaar"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Geen stemdiens nie"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Laat die program toe om dele van ditself deurdringend in die geheue te hou. Dit kan geheue wat aan ander programme beskikbaar is, beperk, en die foon stadiger maak."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"laat loop voorgronddiens"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Laat die program toe om van voorgronddienste gebruik te maak."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"meet programberging-ruimte"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Laat die program toe om sy kode, data en kasgroottes op te haal"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"verander stelsel-instellings"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Hierdie die program kan oudio met die mikrofoon opneem terwyl die program gebruik word."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"neem oudio op die agtergrond op"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Hierdie program kan enige tyd oudio met die mikrofoon opneem."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"stuur bevele na die SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Laat die program toe om bevele na die SIM te stuur. Dit is baie gevaarlik."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"herken fisieke aktiwiteit"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Laat die program toe om videolêers in jou gedeelde berging te lees."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lees prentlêers in gedeelde berging"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Laat die program toe om prentlêers in jou gedeelde berging te lees."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lees prent- en videolêers wat gebruiker in gedeelde berging kies"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Laat die app toe om prent- en videolêers te lees wat jy in jou gedeelde berging kies."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"verander of vee jou gedeelde berging se inhoud uit"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Laat die program toe om jou gedeelde berging se inhoud te skryf."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"maak en/of ontvang SIP-oproepe"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEΪNSTALLEER"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"MAAK TOG OOP"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Skadelike program is bespeur"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gee <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang tot alle toestelloglêers?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Gee eenmalige toegang"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Moenie toelaat nie"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Toestelloglêers teken aan wat op jou toestel gebeur. Programme kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting en daarom moet jy toegang tot alle toestelloglêers net gee aan programme wat jy vertrou. \n\nAs jy nie vir hierdie program toegang tot alle toestelloglêers gee nie, het die program steeds toegang tot eie loglêers. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Toestelloglêers teken aan wat op jou toestel gebeur. Programme kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting, en daarom moet jy toegang tot alle toestelloglêers net gee aan programme wat jy vertrou. \n\nHierdie program het steeds toegang tot eie loglêers as jy nie vir hierdie program toegang tot alle toestelloglêers gee nie. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel.\n\nKom meer te wete by g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Moenie weer wys nie"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wil <xliff:g id="APP_2">%2$s</xliff:g>-skyfies wys"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Wysig"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Oproepe en kennisgewings sal vibreer"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kan nie toegang tot die foon se kamera op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kan nie toegang tot die tablet se kamera op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Jy kan nie toegang hiertoe kry terwyl daar gestroom word nie. Probeer eerder op jou foon."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Stelselverstek"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 2574fd8..9a3cfd0 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"የደዋይ ID ነባሪዎች ወደአልተከለከለም። ቀጥሎ ጥሪ፡አልተከለከለም"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"አገልግሎት አልቀረበም።"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"የደዋይ መታወቂያ ቅንብሮች መለወጥ አትችልም፡፡"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"ውሂብ ወደ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ተቀይሯል"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"ይህን በማንኛውም ጊዜ በቅንብሮች ውስጥ መለወጥ ይችላሉ"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ምንም የተንቀሳቃሽ ስልክ ውሂብ አገልግሎት የለም"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"የድንገተኛ አደጋ ጥሪ አይገኝም"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ምንም የድምፅ ጥሪ አገልግሎት የለም"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"መተግበሪያው የራሱን ክፍሎች በማህደረ ትውስታ ውስጥ በቋሚነት የሚቀጥሉ እንዲያደርግ ይፈቅድለታል። ይህ ለሌላ መተግበሪያዎች ያለውን ማህደረ ትውስታ በመገደብ ስልኩን ያንቀራፍፈዋል።"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"የፊት አገልግሎትን ማሄድ"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"መተግበሪያው ፊት ላይ ያሉ አገልግሎቶችን እንዲጠቀም ያስችለዋል።"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"የመተግበሪያ ማከማቻ ቦታ ለካ"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"የራሱን ኮድ ፣ውሂብ እና መሸጎጫ መጠኖች ሰርስሮ ለማውጣት ለመተግበሪያው ይፈቅዳሉ።"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"የስርዓት ቅንብሮችን አስተካክል"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ይህ መተግበሪያ መተግበሪያው ስራ ላይ ሳለ ማይክሮፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"በበስተጀርባ ኦዲዮን ይቅዱ"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ይህ መተግበሪያ በማናቸውም ጊዜ ማይክራፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"ወደ ሲሙ ትዕዛዞችን መላክ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"መተግበሪያው ትዕዛዞችን ወደ ሲሙ እንዲልክ ያስችለዋል። ይሄ በጣማ አደገኛ ነው።"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"አካላዊ እንቅስቃሴን ለይቶ ማወቅ"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"መተግበሪያው ከእርስዎ የተጋራ ማከማቻ የቪዲዮ ፋይሎችን እንዲያነብ ይፈቅድለታል።"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ከጋራ ማከማቻ የምስል ፋይሎችን አንብብ"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"መተግበሪያው ከእርስዎ የተጋራ ማከማቻ የምስል ፋይሎችን እንዲያነብ ይፈቅድለታል።"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ከተጋራ ማከማቻው ውስጥ በተጠቃሚ የተመረጡ የምስል እና የቪድዮ ፋይሎችን አንብብ"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ከእርስዎ የተጋራ ማከማቻ እርስዎ የሚመርጧቸው የምስል እና የቪድዮ ፋይሎችን መተግበሪያው እንዲያነብ ይፈቅድለታል።"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"የተጋራ ማከማቻዎን ይዘቶች ይቀይሩ ወይም ይሰርዙ"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"መተግበሪያው የእርስዎን የተጋራ ማከማቻ ይዘቶችን እንዲጽፍ ያስችለዋል።"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"የSIP ጥሪዎችን ያድርጉ/ይቀበሉ"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"አራግፍ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ለማንኛውም ክፈት"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ጎጂ መተግበሪያ ተገኝቷል"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ይፈቀድለት?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"የአንድ ጊዜ መዳረሻን ፍቀድ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"አትፍቀድ"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸውን መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የእርስዎ መሣሪያ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸው መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የመሣሪያዎ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።\n\ng.co/android/devicelogs ላይ የበለጠ ይወቁ።"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ዳግም አታሳይ"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን ማሳየት ይፈልጋል"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"አርትዕ"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ጥሪዎች እና ማሳወቂያዎች ይነዝራሉ"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"የስልኩን ካሜራ ከእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> መድረስ አይቻልም"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ጡባዊውን ካሜራ ከእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> መድረስ አይቻልም"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ዥረት በመልቀቅ ላይ ሳለ ይህ ሊደረስበት አይችልም። በምትኩ በስልክዎ ላይ ይሞክሩ።"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"የሥርዓት ነባሪ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ካርድ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index ba3ebb1..d142993 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -76,10 +76,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"الإعداد التلقائي لمعرف المتصل هو غير محظور . الاتصال التالي: غير محظور"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"الخدمة غير متوفرة."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"لا يمكنك تغيير إعداد معرّف المتصل."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"تم تبديل البيانات إلى <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"يمكنك تغيير هذه الميزة في أي وقت في الإعدادات."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"لا تتوفّر خدمة بيانات جوّال."</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"لا تتوفّر مكالمات طوارئ."</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"لا تتوفر خدمة صوتية"</string>
@@ -401,6 +399,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"للسماح للتطبيق بجعل أجزاء منه ثابتة في الذاكرة. وقد يؤدي هذا إلى تقييد الذاكرة المتاحة للتطبيقات الأخرى مما يؤدي إلى حدوث بطء في الهاتف."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"تشغيل الخدمة في المقدمة"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"للسماح للتطبيق بالاستفادة من الخدمات التي تعمل في المقدمة."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"قياس مساحة تخزين التطبيق"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"للسماح للتطبيق باسترداد شفرته وبياناته وأحجام ذاكرات التخزين المؤقت"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"تعديل إعدادات النظام"</string>
@@ -453,6 +499,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون عندما يكون التطبيق قيد الاستخدام."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"تسجيل الصوت في الخلفية"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون في أي وقت."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"إرسال أوامر إلى شريحة SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"السماح للتطبيق بإرسال أوامر إلى شريحة SIM. وهذا أمر بالغ الخطورة."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"التعرّف على النشاط البدني"</string>
@@ -704,6 +754,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"يسمح للتطبيق بقراءة ملفات الفيديو من مساحة التخزين المشتركة."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"قراءة ملفات الصور من مساحة التخزين المشتركة"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"يسمح هذا الإذن للتطبيق بقراءة ملفات الصور من مساحة التخزين المشتركة."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"قراءة ملفات الفيديو والصور من مساحة التخزين المشتركة"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"يسمح هذا الإذن للتطبيق بقراءة ملفات الفيديو والصور التي تختارها من مساحة التخزين المشتركة."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"تعديل محتوى مساحة التخزين المشتركة أو حذفه"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"للسماح للتطبيق بالكتابة إلى محتوى مساحة التخزين المشتركة."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"إجراء/تلقي مكالمات SIP"</string>
@@ -2054,12 +2106,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"إلغاء التثبيت"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"الفتح على أي حال"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"تم العثور على تطبيق ضار"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"هل تريد السماح لتطبيق <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> بالوصول إلى جميع سجلّات الجهاز؟"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"السماح بالوصول إلى السجلّ لمرة واحدة"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"عدم السماح"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. ويظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلّها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. وقد يظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك.\n\nتعرَّف على مزيد من المعلومات على الرابط g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"عدم الإظهار مرة أخرى"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"يريد تطبيق <xliff:g id="APP_0">%1$s</xliff:g> عرض شرائح تطبيق <xliff:g id="APP_2">%2$s</xliff:g>."</string>
<string name="screenshot_edit" msgid="7408934887203689207">"تعديل"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"سيهتز الهاتف عند تلقّي المكالمات والإشعارات."</string>
@@ -2300,6 +2346,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"يتعذّر الوصول إلى كاميرا الهاتف من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"يتعذّر الوصول إلى كاميرا الجهاز اللوحي من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"لا يمكن الوصول إلى هذا المحتوى أثناء البث. بدلاً من ذلك، جرِّب استخدام هاتفك."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"الإعداد التلقائي للنظام"</string>
<string name="default_card_name" msgid="9198284935962911468">"رقم البطاقة <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index d49d3b1..2d740ec 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"কলাৰ আইডি সীমিত নকৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হোৱা নাই"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"সুবিধা যোগান ধৰা হোৱা নাই।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"আপুনি কলাৰ আইডি ছেটিং সলনি কৰিব নোৱাৰে।"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"ডেটা <xliff:g id="CARRIERDISPLAY">%s</xliff:g>লৈ সলনি কৰা হৈছে"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"আপুনি ছেটিঙত এইটো যিকোনো সময়তে সলনি কৰিব পাৰে"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"কোনো ম’বাইল ডেটা সেৱা নাই"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"জৰুৰীকালীন কল কৰাৰ সুবিধা উপলব্ধ নহয়"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"কোনো ভইচ সেৱা নাই"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"মেম\'ৰিত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ এপক অনুমতি দিয়ে। এই কার্যই ফ\'নৰ কার্যক লেহেমীয়া কৰি অন্য এপবোৰৰ বাবে উপলব্ধ মেম\'ৰিক সীমাবদ্ধ কৰে।"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"অগ্ৰভূমিৰ সেৱা চলাব পাৰে"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"এপ্টোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"এপৰ ষ্ট’ৰেজৰ খালী ঠাই হিচাপ কৰক"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"এপ্টোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ছিষ্টেম ছেটিংহ সংশোধন কৰক"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"এই এপ্টোৱে ইয়াক ব্যৱহাৰ কৰি থাকোঁতে মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰি অডিঅ’ ৰেকর্ড কৰিব পাৰে।"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"নেপথ্যত অডিঅ’ ৰেকৰ্ড কৰক"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"এই এপ্টোৱে যিকোনো সময়তে মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰি অডিঅ’ ৰেকৰ্ড কৰিব পাৰে।"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"ছিমলৈ নিৰ্দেশ পঠিয়াব পাৰে"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ছিমলৈ নিৰ্দেশসমূহ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ই অতি ক্ষতিকাৰক।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"শাৰীৰিক কাৰ্যকলাপ চিনাক্ত কৰক"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ পৰা ভিডিঅ’ ফাইল পঢ়িবলৈ এপ্টোক অনুমতি দিয়ে।"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ পৰা প্ৰতিচ্ছবিৰ ফাইল পঢ়ক"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ পৰা প্ৰতিচ্ছবিৰ ফাইল পঢ়িবলৈ এপ্টোক অনুমতি দিয়ে।"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ব্যৱহাৰকাৰীয়ে শ্বেয়াৰ কৰা ষ্ট’ৰেজৰ পৰা বাছনি কৰা প্ৰতিচ্ছবি আৰু ভিডিঅ’ ফাইলসমূহ পঢ়া"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"আপুনি নিজৰ শ্বেয়াৰ কৰা ষ্ট’ৰেজৰ পৰা বাছনি কৰা প্ৰতিচ্ছবি আৰু ভিডিঅ’ ফাইলসমূহ পঢ়িবলৈ এপ্টোক অনুমতি দিয়ে।"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল সংশোধন কৰিব বা মচিব পাৰে"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল লিখিবলৈ এপ্টোক অনুমতি দিয়ে।"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP কল কৰা/পোৱা"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"আনইনষ্টল কৰক"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"যিহ\'লেও খোলক"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ক্ষতিকাৰক এপ্ চিনাক্ত কৰা হৈছে"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ক আটাইবোৰ ডিভাইচৰ লগ এক্সেছ কৰাৰ অনুমতি প্ৰদান কৰিবনে?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"কেৱল এবাৰ এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"অনুমতি নিদিব"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।\n\ng.co/android/devicelogsত অধিক জানক।"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"পুনৰ নেদেখুৱাব"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>এ <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাব খুজিছে"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"সম্পাদনা কৰক"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"কল আৰু জাননীসমূহে কম্পন কৰিব"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ৰ পৰা ফ’নটোৰ কেমেৰা এক্সেছ কৰিব নোৱাৰি"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ৰ পৰা টেবলেটটোৰ কেমেৰা এক্সেছ কৰিব নোৱাৰি"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ষ্ট্ৰীম কৰি থকাৰ সময়ত এইটো এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"ছিষ্টেম ডিফ’ল্ট"</string>
<string name="default_card_name" msgid="9198284935962911468">"কাৰ্ড <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index ffd1480..772bbb4 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağan deyil"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Xidmət təmin edilməyib."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Çağrı kimliyi ayarını dəyişə bilməzsiniz."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Data <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoruna keçirilib"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Bunu istənilən zaman Ayarlarda dəyişə bilərsiniz"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil data xidməti yoxdur"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Təcili zəng əlçatan deyil"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Səsli xidmət yoxdur"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Tətbiqə öz komponentlərini yaddaşda saxlama icazəsi verir. Bu digər tətbiqlər üçün mövcud olan yaddaşı limitləyə bilər."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ön fon xidmətindən istifadə edin"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Tətbiqə ön fon xidmətlərini işlətmək icazəsi verin."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"tətbiq saxlama yaddaşını ölçmək"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Tətbiqə özünün kodunu, məlumatını və keş ölçüsünü alma icazəsi verir."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"Sistem ayarlarının dəyişdirilməsi"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu tətbiq istifadə edilən zaman mikrofondan istifadə edərək audio yaza bilər."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"arxa fonda audio yazmaq"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu tətbiq istənilən zaman mikrofondan istifadə edərək audio yaza bilər."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"əmrləri SIM\'ə göndərin"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Tətbiqə SIM-ə əmrlər göndərməyə imkan verir. Bu, çox təhlükəlidir."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziki fəaliyyəti tanıyın"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Tətbiqə paylaşılan yaddaşdakı video fayllarını oxumaq imkanı verir."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"paylaşılan yaddaşdakı şəkil fayllarını oxumaq"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Tətbiqə paylaşılan yaddaşdakı şəkil fayllarını oxumaq imkanı verir."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"paylaşılan yaddaşdan istifadəçinin seçdiyi şəkil və video fayllarını oxumaq"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Tətbiqə paylaşılan yaddaşdan seşdiyiniz şəkil və video fayllarını oxumaq imkanı verir."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"paylaşılan yaddaşdakı kontenti dəyişmək və ya silmək"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Tətbiqə paylaşılan yaddaşdakı kontenti yazmaq imkanı verir."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP çağrıları göndərin/qəbul edin"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"SİSTEMDƏN SİLİN"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"İSTƏNİLƏN HALDA AÇIN"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Zərərli tətbiq aşkarlandı"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tətbiqinin bütün cihaz qeydlərinə girişinə icazə verilsin?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Birdəfəlik girişə icazə verin"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"İcazə verməyin"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər.\n\nƏtraflı məlumat: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Daha göstərməyin"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> tətbiqindən bölmələr göstərmək istəyir"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Redaktə edin"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Zəng və bildirişlər vibrasiya verəcək"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan telefonun kamerasına giriş etmək olmur"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan planşetin kamerasına giriş etmək olmur"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Yayım zamanı buna giriş mümkün deyil. Telefonunuzda sınayın."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Sistem defoltu"</string>
<string name="default_card_name" msgid="9198284935962911468">"KART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index b1ec940b..13523f8 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: Nije ograničen."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije dobavljena."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ne možete da promenite podešavanje ID-a korisnika."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobilni podaci su prebačeni na operatera <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Ovo možete u svakom trenutku da promenite u Podešavanjima"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema usluge mobilnih podataka"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi nisu dostupni"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema glasovne usluge"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Dozvoljava aplikaciji da učini sopstvene komponente trajnim u memoriji. Ovo može da ograniči memoriju dostupnu drugim aplikacijama i uspori telefon."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"pokreni uslugu u prvom planu"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Dozvoljava aplikaciji da koristi usluge u prvom planu."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"merenje memorijskog prostora u aplikaciji"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Dozvoljava aplikaciji da preuzme veličine kôda, podataka i keša."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"izmena podešavanja sistema"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Ova aplikacija može da snima zvuk pomoću mikrofona dok se aplikacija koristi."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"da snima zvuk u pozadini"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ova aplikacija može da snima zvuk pomoću mikrofona u bilo kom trenutku."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi na SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji da šalje komande SIM kartici. To je veoma opasno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičkih aktivnosti"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Omogućava aplikaciji da čita video fajlove iz deljenog memorijskog prostora."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"čitanje fajlova slika iz deljenog memorijskog prostora"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Omogućava aplikaciji da čita fajlove slika iz deljenog memorijskog prostora."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čitanje fajlova slika i video snimaka koje korisnik bira iz deljenog memorijskog prostora"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Omogućava aplikaciji da čita fajlove slika i video snimaka koje izaberete iz deljenog memorijskog prostora."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"menjanje ili brisanje sadržaja deljenog memorijskog prostora"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Dozvoljava aplikaciji da upisuje sadržaj deljenog memorijskog prostora."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"upućivanje/prijem SIP poziva"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALIRAJ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IPAK OTVORI"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Otkrivena je štetna aplikacija"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Želite da dozvolite aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim evidencijama uređaja?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dozvoli jednokratan pristup"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne dozvoli"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Izmeni"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibracija za pozive i obaveštenja je uključena"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ne može da se pristupi kameri telefona sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ne može da se pristupi kameri tableta sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ovom ne možete da pristupate tokom strimovanja. Probajte na telefonu."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Podrazumevani sistemski"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 40605c5..e2ee1f4 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Налады ідэнтыфікатару АВН па змаўчанні: не абмяжавана. Наступны выклік: не абмежавана"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Служба не прадастаўляецца."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Вы не можаце змяніць налады ідэнтыфікатара абанента, якi тэлефануе."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Мабільны трафік пераключаны на аператара \"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>\""</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Вы можаце змяніць гэта ў любы час у Наладах"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мабільная перадача даных недаступная"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Экстранныя выклікі недаступныя"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Няма сэрвісу галасавых выклікаў"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дазваляе прыкладанню захоўваць некаторыя пастаянныя часткi ў памяцi. Гэта можа абмежаваць аб\'ём памяці, даступнай для іншых прыкладанняў, i запаволiць працу тэлефона."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"запусціць актыўныя сэрвісы"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Дазваляе праграме выкарыстоўваць асноўныя сэрвісы."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"вымерыць прастору для захоўвання прыкладання"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Дазваляе прыкладанням атрымліваць яго код, дадзеныя і аб\'ём кэш-памяці"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"змена сістэмных налад"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Гэта праграма падчас яе выкарыстання можа запісваць аўдыя з дапамогай мікрафона."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"запісваць аўдыя ў фонавым рэжыме"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Гэта праграма можа ў любы час запісваць аўдыя з дапамогай мікрафона."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"адпраўляць каманды на SIM-карту"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Дазваляе праграме адпраўляць каманды SIM-карце. Гэта вельмі небяспечна."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"распазнаваць фізічную актыўнасць"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Праграма зможа счытваць відэафайлы з абагуленага сховішча."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"счытваць файлы відарысаў з абагуленага сховішча"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Праграма зможа счытваць файлы відарысаў з абагуленага сховішча."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"счытваць з абагуленага сховішча выбраныя карыстальнікам файлы відарысаў і відэа"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Праграма зможа счытваць з абагуленага сховішча выбраныя вамі файлы відарысаў і відэа."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"змяненне або выдаленне змесціва абагуленага сховішча"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Дазваляе праграме запісваць змесціва абагуленага сховішча."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"ажыццяўленне/прыманне выклікаў SIP"</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ВЫДАЛІЦЬ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"УСЁ РОЎНА АДКРЫЦЬ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Выяўлена шкодная праграма"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Дазволіць праграме \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" мець доступ да ўсіх журналаў прылады?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дазволіць аднаразовы доступ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дазваляць"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе.\n\nДаведайцеся больш на старонцы g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больш не паказваць"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Праграма <xliff:g id="APP_0">%1$s</xliff:g> запытвае дазвол на паказ зрэзаў праграмы <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Рэдагаваць"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Для выклікаў і апавяшчэнняў уключаны вібрасігнал"</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не ўдалося атрымаць доступ да камеры тэлефона з прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не ўдалося атрымаць доступ да камеры планшэта з прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Не ўдаецца атрымаць доступ у час перадачы плынню. Паспрабуйце скарыстаць тэлефон."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Стандартная сістэмная налада"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index a1c9d1c..0c56d0f 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандартната идентификация на повикванията е „разрешено“. За следващото обаждане тя е разрешена."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е обезпечена."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Не можете да променяте настройката за идентификация на обажданията."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Преминахте към мобилни данни от <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Можете да промените това по всяко време в „Настройки“"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Няма достъп до мобилната услуга за данни"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Няма достъп до спешните обаждания"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Няма услуга за гласови обаждания"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Разрешава на приложението да прави части от себе си постоянни в паметта. Това може да ограничи наличната за другите приложения, забавяйки телефона."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"изпълнение на услуги на преден план"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Разрешава на приложението да се възползва от услуги на преден план."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"измерване на ползваното от приложението място в хранилището"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Разрешава на приложението да извлича размера на своя код, данни и кеш"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"промяна на системните настройки"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Когато се използва, това приложение може да записва аудио посредством микрофона."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"записва аудио на заден план"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Това приложение може по всяко време да записва аудио посредством микрофона."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"изпращане на команди до SIM картата"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Разрешава на приложението да изпраща команди до SIM картата. Това е много опасно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"разпознаване на физическата активност"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Разрешава на приложението да чете видеофайлове от споделеното ви хранилище."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"да чете графични файлове от споделеното хранилище"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Разрешава на приложението да чете графични файлове от споделеното ви хранилище."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"да чете избраните от потребителя графични и видеофайлове от споделеното хранилище"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Разрешава на приложението да чете графичните и видеофайловете, които сте избрали от споделеното си хранилище."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"промяна или изтрив. на съдърж. от сподел. ви хранил."</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Разрешава на прил. да записва съдърж. от сподел. ви хранил."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"извършване/получаване на обаждания чрез SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ДЕИНСТАЛИРАНЕ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ОТВАРЯНЕ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Открито е опасно приложение"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Да се разреши ли на <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> достъп до всички регистрационни файлове за устройството?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешаване на еднократен достъп"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Забраняване"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството пак може да има достъп до някои регистрационни файлове или информация на устройството ви."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството може да има достъп до някои регистрационни файлове или информация на устройството ви.\n\nНаучете повече на адрес g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Да не се показва пак"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> иска да показва части от <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Редактиране"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"При обаждания и известия устройството ще вибрира"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Няма достъп до камерата на телефона от вашия <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Няма достъп до камерата на таблета от вашия <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"До това съдържание не може да се осъществи достъп при поточно предаване. Вместо това опитайте от телефона си."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Стандартно за системата"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index fd69acd..4751df7 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে না৷ পরবর্তী কল: সীমাবদ্ধ নয়"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"পরিষেবা প্রস্তুত নয়৷"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"আপনি কলার আইডি এর সেটিংস পরিবর্তন করতে পারবেন না৷"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>-এর ডেটা ব্যবহার করা হয়েছে"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"আপনি যে কোনও সময় সেটিংস থেকে এটি পরিবর্তন করতে পারবেন"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"মোবাইল ডেটা পরিষেবা নেই"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"জরুরি কল করা যাবে না"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ভয়েস পরিষেবা নেই"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"মেমরিতে নিজের জন্য প্রয়োজনীয় জায়গা আলাদা করে রাখতে অ্যাপ্লিকেশানটিকে মঞ্জুর করে৷ এর ফলে অন্যান্য অ্যাপ্লিকেশানগুলির জায়গা সীমিত হয়ে পড়তে পারে ও ফোনটি অপেক্ষাকৃত ধীরগতির হয়ে পড়তে পারে৷"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ফোরগ্রাউন্ডে পরিষেবা চালানো"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"অ্যাপটিকে ফোরগ্রাউন্ডের পরিষেবা ব্যবহার করতে দেয়।"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"অ্যাপ্লিকেশন সঞ্চয়স্থানের জায়গা পরিমাপ করে"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"অ্যাপ্লিকেশানকে এটির কোড, ডেটা, এবং ক্যাশে মাপ উদ্ধার করার অনুমতি দেয়"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"সিস্টেম সেটিংস পরিবর্তন করুন"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"এই অ্যাপটি যখন ব্যবহার করা হচ্ছে, তখন মাইক্রোফোন ব্যবহার করে অডিও রেকর্ড করতে পারবে।"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ব্যাকগ্রাউন্ডে অডিও রেকর্ড করতে পারবে"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"এই অ্যাপটি মাইক্রোফোন ব্যবহার করে যেকোনও সময় অডিও রেকর্ড করতে পারবে।"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"সিম এ আদেশগুলি পাঠান"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"অ্যাপ্লিকেশানটিকে সিম কার্ডে কমান্ডগুলি পাঠানোর অনুমতি দেয়৷ এটি খুবই বিপজ্জনক৷"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"শারীরিক অ্যাক্টিভিটি শনাক্ত করুন"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"আপনার শেয়ার করা স্টোরেজ থেকে ভিডিও ফাইল রিড করতে অ্যাপকে অনুমতি দিন।"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"শেয়ার করা স্টোরেজ থেকে ছবির ফাইল রিড করা"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"আপনার শেয়ার করা স্টোরেজ থেকে ছবির ফাইল রিড করার জন্য অ্যাপকে অনুমতি দেয়।"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"শেয়ার করা স্টোরেজ থেকে ব্যবহারকারীর বেছে নেওয়া ছবি ও ভিডিওর ফাইল রিড করুন"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"আপনার শেয়ার করা স্টোরেজ থেকে ছবি ও ভিডিওর ফাইল রিড করার জন্য অ্যাপকে অনুমতি দেয়।"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"শেয়ার করা স্টোরেজের কন্টেন্ট মুছে ফেলুন বা পরিবর্তন করুন"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"শেয়ার করা স্টোরেজের কন্টেন্ট লেখার জন্য অ্যাপটিকে অনুমতি দিন।"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP কল করুন/গ্রহণ করুন"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"আন-ইনস্টল করুন"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"যাই হোক, খুলতে চাই"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ক্ষতিকর অ্যাপ শনাক্ত করা হয়েছে"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেসের অনুমতি দিতে চান?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"এককালীন অ্যাক্সেসের অনুমতি দিন"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"অনুমতি দেবেন না"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই সব ডিভাইসের লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজের লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারকও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ, সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই ডিভাইসের সব লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজের লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারক এখনও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।\n\ng.co/android/devicelogs লিঙ্ক থেকে আরও জানুন।"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"আর দেখতে চাই না"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটি <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখাতে চায়"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"এডিট করুন"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"কল এবং বিজ্ঞপ্তি আসলে ভাইব্রেট হবে"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"আপনার <xliff:g id="DEVICE">%1$s</xliff:g> থেকে ফোনের ক্যামেরা অ্যাক্সেস করা যাচ্ছে না"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"আপনার <xliff:g id="DEVICE">%1$s</xliff:g> থেকে ট্যাবলেটের ক্যামেরা অ্যাক্সেস করা যাচ্ছে না"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"স্ট্রিমিংয়ের সময় এটি অ্যাক্সেস করা যাবে না। পরিবর্তে আপনার ফোনে ব্যবহার করে দেখুন।"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"সিস্টেম ডিফল্ট"</string>
<string name="default_card_name" msgid="9198284935962911468">"কার্ড <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 455c9e9..acd9aa4 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -73,8 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Prikaz ID-a pozivaoca u zadanim postavkama nije zabranjen. Sljedeći poziv: nije zabranjen"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Uslugu nije moguće koristiti."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavke ID-a pozivaoca."</string>
- <string name="auto_data_switch_title" msgid="3286350716870518297">"Podaci su prebačeni na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
- <string name="auto_data_switch_content" msgid="803557715007110959">"To uvijek možete promijeniti u postavkama"</string>
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Prijenos podataka usmjeravanjem na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Ove postavke možete uvijek promijeniti u Postavkama"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema usluge prijenosa podataka na mobilnoj mreži"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi su nedostupni"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema usluge govornih poziva"</string>
@@ -232,7 +232,7 @@
<string name="shutdown_confirm" product="default" msgid="136816458966692315">"Telefon će se isključiti."</string>
<string name="shutdown_confirm_question" msgid="796151167261608447">"Želite li ugasiti telefon?"</string>
<string name="reboot_safemode_title" msgid="5853949122655346734">"Ponovo pokreni uređaj u sigurnom načinu rada"</string>
- <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Želite li pokrenuti uređaj u sigurnom načinu rada? To će onemogućiti sve aplikacije trećih strana koje ste instalirali. One će biti obnovljene kada ponovo pokrenete uređaj."</string>
+ <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Želite li ponovo pokrenuti uređaj u sigurnom načinu rada? To će onemogućiti sve aplikacije trećih strana koje ste instalirali. Obnovit će se kada još jednom ponovo pokrenete uređaj."</string>
<string name="recent_tasks_title" msgid="8183172372995396653">"Nedavni zadaci"</string>
<string name="no_recent_tasks" msgid="9063946524312275906">"Nema nedavno pokrenutih aplikacija."</string>
<string name="global_actions" product="tablet" msgid="4412132498517933867">"Opcije tableta"</string>
@@ -396,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Omogućava aplikaciji da neke svoje dijelove pohrani trajno u memoriji. Ovo može ograničiti veličinu raspoložive memorije za druge aplikacije i tako usporiti telefon."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"pokretanje usluge u prvom planu"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Dopušta aplikaciji korištenje usluga u prvom planu."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora kojeg aplikacije zauzimaju u pohrani"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Dozvoljava aplikaciji preuzimanje svog koda, podataka i veličine keš memorije"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"izmjena postavki sistema"</string>
@@ -448,6 +496,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Za vrijeme korištenja, ova aplikacija može snimati zvuk koristeći mikrofon."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snimanje zvuka u pozadini"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ova aplikacija može u svakom trenutku snimati zvuk koristeći mikrofon."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detektirati snimanja zaslona prozora aplikacije"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ako se tijekom upotrebe ove aplikacije izradi snimka zaslona, aplikacija će dobiti obavijest."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi SIM kartici"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji slanje naredbi na SIM. Ovo je vrlo opasno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičke aktivnosti"</string>
@@ -699,6 +749,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Omogućava aplikaciji da čita fajlove videozapisa iz vaše dijeljene pohrane."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"čitanje fajlova slika iz dijeljene pohrane"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Omogućava aplikaciji da čita fajlove slika iz vaše dijeljene pohrane."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čitanje fajlova slika i videozapisa iz dijeljene pohrane koje je odabrao korisnik"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Omogućava aplikaciji da čita fajlove slika i videozapisa koje odaberete iz vaše dijeljene pohrane."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"mijenja ili briše sadržaj vaše dijeljene pohrane"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Omogućava aplikaciji da piše sadržaj vaše dijeljene pohrane."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"Uputi/primi SIP pozive"</string>
@@ -1013,7 +1065,7 @@
<string name="factorytest_failed" msgid="3190979160945298006">"Fabrički test nije uspio"</string>
<string name="factorytest_not_system" msgid="5658160199925519869">"Akcija FACTORY_TEST podržana je samo za pakete instalirane u facsikli /system/app."</string>
<string name="factorytest_no_action" msgid="339252838115675515">"Nije pronađen paket koji omogućava akciju FACTORY_TEST."</string>
- <string name="factorytest_reboot" msgid="2050147445567257365">"Ponovno pokretanje"</string>
+ <string name="factorytest_reboot" msgid="2050147445567257365">"Ponovo pokreni"</string>
<string name="js_dialog_title" msgid="7464775045615023241">"Stranica na \"<xliff:g id="TITLE">%s</xliff:g>\" kaže:"</string>
<string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Potvrdite navigaciju"</string>
@@ -1374,7 +1426,7 @@
<string name="console_running_notification_title" msgid="6087888939261635904">"Serijska konzola omogućena"</string>
<string name="console_running_notification_message" msgid="7892751888125174039">"Performanse su smanjene. Da onemogućite, provjerite program za učitavanje operativnog sistema."</string>
<string name="mte_override_notification_title" msgid="4731115381962792944">"Eksperimentalni MTE je omogućen"</string>
- <string name="mte_override_notification_message" msgid="2441170442725738942">"Moguće da će to uticati na performanse i stabilnost. Ponovo pokrenite da onemogućite. Ako je omogućeno pomoću arm64.memtag.bootctl, unaprijed ga postavite na \"Ništa\"."</string>
+ <string name="mte_override_notification_message" msgid="2441170442725738942">"To može uticati na performanse i stabilnost. Ponovo pokrenite da onemogućite. Ako je omogućeno pomoću arm64.memtag.bootctl, prvo postavite na \"Ništa\"."</string>
<string name="usb_contaminant_detected_title" msgid="4359048603069159678">"Tečnost ili nečistoće u USB priključku"</string>
<string name="usb_contaminant_detected_message" msgid="7346100585390795743">"USB priključak je automatski onemogućen. Dodirnite da saznate više."</string>
<string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"USB priključak je sada sigurno koristiti"</string>
@@ -2049,12 +2101,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALIRAJ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IPAK OTVORI"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Otkrivena je štetna aplikacija"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Dozvoliti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dozvoli jednokratan pristup"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nemoj dozvoliti"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i isprave probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, zato pristup svim zapisnicima uređaja dozvolite samo aplikacijama kojima vjerujete. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje biti u stanju pristupiti nekim zapisnicima ili podacima na uređaju."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i riješe probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke. Zbog toga pristup svim zapisnicima uređaja dozvolite samo aplikacijama koje smatrate pouzdanima. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje moći pristupiti nekim zapisnicima ili podacima na uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Pozivi i obavještenja će vibrirati"</string>
@@ -2295,6 +2341,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nije moguće pristupiti kameri telefona s uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nije moguće pristupiti kameri tableta s uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ovom ne možete pristupiti tokom prijenosa. Umjesto toga pokušajte na telefonu."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Sistemski zadano"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 43dc676..18abb28 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El valor predeterminat de l\'identificador de trucada és no restringit. Trucada següent: no restringit"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"No s\'ha proveït el servei."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"No pots canviar la configuració de l\'identificador de trucada."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Les dades s\'han canviat a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Pots canviar aquesta opció en qualsevol moment a Configuració"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hi ha servei de dades mòbils"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Les trucades d\'emergència no estan disponibles"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sense servei de veu"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permet que l\'aplicació faci que parts de la seva memòria siguin persistents. Aquesta acció pot limitar la memòria disponible per a altres aplicacions i alentir el telèfon."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"executar serveis en primer pla"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permet que l\'aplicació utilitzi serveis en primer pla."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mesura l\'espai d\'emmagatzematge d\'aplicacions"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permet que l\'aplicació recuperi les mides del codi, de les dades i de la memòria cau"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar la configuració del sistema"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Aquesta aplicació pot gravar àudio amb el micròfon mentre s\'està utilitzant."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar àudio en segon pla"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aquesta aplicació pot gravar àudio amb el micròfon en qualsevol moment."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar ordres a la SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconèixer l\'activitat física"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permet que l\'aplicació llegeixi fitxers de vídeo de l\'emmagatzematge compartit."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"llegir fitxers d\'imatge de l\'emmagatzematge compartit"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permet que l\'aplicació llegeixi fitxers d\'imatge de l\'emmagatzematge compartit."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"llegir els fitxers d\'imatge i de vídeo que l\'usuari seleccioni de l\'emmagatzematge compartit"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permet que l\'aplicació llegeixi els fitxers d\'imatge i de vídeo que seleccionis de l\'emmagatzematge compartit."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"editar o suprimir cont. d\'emmagatzematge compartit"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"L\'app pot editar contingut de l\'emmagatzematge compartit."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"Fer i rebre trucades de SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTAL·LA"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OBRE IGUALMENT"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"S\'ha detectat una aplicació perjudicial"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vols permetre que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> accedeixi a tots els registres del dispositiu?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permet l\'accés únic"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permetis"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació pugui accedir a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació accedeixi a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu.\n\nObtén més informació a g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No tornis a mostrar"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vol mostrar porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edita"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Les trucades i les notificacions vibraran"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No es pot accedir a la càmera del telèfon des del teu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No es pot accedir a la càmera de la tauleta des del teu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"No s\'hi pot accedir mentre s\'està reproduint en continu. Prova-ho al telèfon."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Valor predeterminat del sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARGETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 132a9f5..eae6dfb 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ve výchozím nastavení není funkce ID volajícího omezena. Příští hovor: Neomezeno"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba není zřízena."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nastavení ID volajícího nesmíte měnit."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Datové připojení bylo přepnuto na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Volbu můžete kdykoli změnit v nastavení"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Není k dispozici žádná mobilní datová služba"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Tísňová volání jsou nedostupná"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hlasová volání nejsou k dispozici"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Umožňuje aplikaci uložit některé své části trvale do paměti. Může to omezit paměť dostupnou pro ostatní aplikace a zpomalit tak telefon."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"spouštění služeb na popředí"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Povolte aplikaci využívání služeb na popředí."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"výpočet místa pro ukládání aplikací"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Umožňuje aplikaci načtení svého kódu, dat a velikostí mezipaměti."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"změna nastavení systému"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Tato aplikace může pomocí mikrofonu během svého používání zaznamenat zvuk."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"zaznamenávat zvuk na pozadí"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Tato aplikace může pomocí mikrofonu kdykoli zaznamenat zvuk."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"odesílání příkazů do SIM karty"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikaci odesílat příkazy na kartu SIM. Toto oprávnění je velmi nebezpečné."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávání fyzické aktivity"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Umožňuje aplikaci čtení videosouborů ze sdíleného úložiště."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"čtení obrázkových souborů ze sdíleného úložiště"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Umožňuje aplikaci číst obrázkové soubory ze sdíleného úložiště."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čtení vybraných obrázkových souborů a souborů videa ze sdíleného úložiště"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Umožňuje aplikaci číst vybrané obrázkové soubory a soubory videa ze sdíleného úložiště."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"úprava nebo mazání obsahu sdíleného úložiště"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Umožňuje aplikaci zápis obsahu do sdíleného úložiště."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"uskutečňování/příjem volání SIP"</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODINSTALOVAT"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"PŘESTO OTEVŘÍT"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Byla zjištěna škodlivá aplikace"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Povolit aplikaci <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> přístup ke všem protokolům zařízení?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Povolit jednorázový přístup"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nepovolovat"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení.\n\nDalší informace najdete na stránce g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Příště nezobrazovat"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Aplikace <xliff:g id="APP_0">%1$s</xliff:g> chce zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Upravit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Volání a oznámení budou vibrovat"</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ze zařízení <xliff:g id="DEVICE">%1$s</xliff:g> nelze získat přístup k fotoaparátu telefonu"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ze zařízení <xliff:g id="DEVICE">%1$s</xliff:g> nelze získat přístup k fotoaparátu tabletu"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Tento obsah při streamování nelze zobrazit. Zkuste to na telefonu."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Výchozí nastavení systému"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 9dd4334..5e32ab6 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -334,7 +334,7 @@
<string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Udføre bevægelser"</string>
<string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan trykke, stryge, knibe sammen og udføre andre bevægelser."</string>
<string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingeraftryksbevægelser"</string>
- <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrere bevægelser, der foretages på enhedens fingeraftrykslæser."</string>
+ <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrere bevægelser, der foretages på enhedens fingeraftrykssensor."</string>
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Tag screenshot"</string>
<string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan tage et screenshot af skærmen."</string>
<string name="permlab_statusBar" msgid="8798267849526214017">"deaktivere eller redigere statuslinje"</string>
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Tillader, at appen gør dele af sig selv vedholdende i hukommelsen. Dette kan begrænse den tilgængelige hukommelse for andre apps, hvilket gør telefonen langsommere."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"kør tjeneste i forgrunden"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Tillad, at appen anvender tjenester i forgrunden."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"måle appens lagerplads"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Tillader, at en app kan hente sin kode, data og cachestørrelser"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ændre systemindstillinger"</string>
@@ -447,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Denne app kan optage lyd med mikrofonen, mens appen er i brug."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"optag lyd i baggrunden"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Denne app kan optage lyd med mikrofonen når som helst."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"send kommandoer til SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillader, at appen sender kommandoer til SIM-kortet. Dette er meget farligt."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"genkend fysisk aktivitet"</string>
@@ -583,11 +635,11 @@
<string name="biometric_error_generic" msgid="6784371929985434439">"Der opstod fejl i forbindelse med godkendelse"</string>
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Brug skærmlås"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Angiv din skærmlås for at fortsætte"</string>
- <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Hold fingeren nede på læseren"</string>
+ <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Hold fingeren nede på sensoren"</string>
<string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeraftrykket kan ikke genkendes. Prøv igen."</string>
- <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengør fingeraftrykslæseren, og prøv igen"</string>
- <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengør læseren, og prøv igen"</string>
- <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Hold fingeren nede på læseren"</string>
+ <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengør fingeraftrykssensoren, og prøv igen"</string>
+ <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengør sensoren, og prøv igen"</string>
+ <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Hold fingeren nede på sensoren"</string>
<string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Du bevægede fingeren for langsomt. Prøv igen."</string>
<string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Prøv med et andet fingeraftryk"</string>
<string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Der er for lyst"</string>
@@ -610,9 +662,9 @@
<string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"Du har brugt for mange forsøg. Brug skærmlåsen i stedet."</string>
<string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"Fingeraftrykket kan ikke behandles. Prøv igen."</string>
<string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Der er ikke registreret nogen fingeraftryk."</string>
- <string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Denne enhed har ingen fingeraftrykslæser."</string>
+ <string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Denne enhed har ingen fingeraftrykssensor."</string>
<string name="fingerprint_error_security_update_required" msgid="7750187320640856433">"Sensoren er midlertidigt deaktiveret."</string>
- <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Fingeraftrykslæseren kan ikke bruges. Få den repareret"</string>
+ <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Fingeraftrykssensoren kan ikke bruges. Få den repareret"</string>
<string name="fingerprint_error_power_pressed" msgid="5479524500542129414">"Der blev trykket på afbryderknappen"</string>
<string name="fingerprint_name_template" msgid="8941662088160289778">"Fingeraftryk <xliff:g id="FINGERID">%d</xliff:g>"</string>
<string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Brug fingeraftryk"</string>
@@ -632,7 +684,7 @@
<string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfigurer flere måder at låse op på"</string>
<string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tryk for at tilføje et fingeraftryk"</string>
<string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Oplåsning med fingeraftryk"</string>
- <string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Fingeraftrykslæseren kan ikke bruges"</string>
+ <string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Fingeraftrykssensoren kan ikke bruges"</string>
<string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Få den repareret."</string>
<string name="face_acquired_insufficient" msgid="6889245852748492218">"Din ansigtsmodel kan ikke oprettes. Prøv igen."</string>
<string name="face_acquired_too_bright" msgid="8070756048978079164">"Der er for lyst. Prøv en mere dæmpet belysning."</string>
@@ -698,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Tillader, at appen læser videofiler fra din fælles lagerplads."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"Læse billedfiler fra den delte lagerplads"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Tillader, at appen læser billedfiler fra din delte lagerplads."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"læse brugervalgte billed- og videofiler fra delt lagerplads"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Tillader, at appen læser billed- og videofiler, som du vælger fra din delte lagerplads."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ændre eller slette indholdet af din delte lagerplads"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Tillader, at appen kan skrive indholdet af din delte lagerplads."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"foretage/modtage SIP-opkald"</string>
@@ -2048,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"AFINSTALLER"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ÅBN ALLIGEVEL"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Der er registreret en skadelig app"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vil du give <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> adgang til alle enhedslogs?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tillad engangsadgang"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tillad ikke"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed.\n\nFå flere oplysninger på g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Vis ikke igen"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> anmoder om tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Rediger"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Telefonen vibrerer ved opkald og notifikationer"</string>
@@ -2294,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kameraet på din telefon kan ikke tilgås via din <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kameraet på din tablet kan ikke tilgås via din <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Der er ikke adgang til dette indhold under streaming. Prøv på din telefon i stedet."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Systemstandard"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 661c2cd..c4da5c8 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Nicht beschränkt"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Dienst nicht eingerichtet."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Du kannst die Einstellung für die Anrufer-ID nicht ändern."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobile Daten wurden auf <xliff:g id="CARRIERDISPLAY">%s</xliff:g> umgestellt"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Du kannst dies jederzeit in den Einstellungen ändern"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Kein mobiler Datendienst"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Notrufe nicht möglich"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Keine Anrufe"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ermöglicht der App, Teile der eigenen App dauerhaft im Speicher abzulegen. Dies kann dazu führen, dass anderen Apps weniger Arbeitsspeicher zur Verfügung steht und das Telefon langsamer wird."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"Vordergrunddienst ausführen"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Ermöglicht der App, die Vordergrunddienste zu verwenden."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"Speicherplatz der App ermitteln"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Ermöglicht der App, ihre Code-, Daten- und Cache-Größe abzurufen"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"Systemeinstellungen ändern"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Diese App darf mit dem Mikrofon Audioaufnahmen machen, solange sie verwendet wird."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Audioaufnahmen im Hintergrund machen"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Diese App darf mit dem Mikrofon jederzeit Audioaufnahmen machen."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"Befehle an die SIM senden"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Ermöglicht der App das Senden von Befehlen an die SIM-Karte. Dies ist äußerst risikoreich."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"Körperliche Aktivitäten erkennen"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Gewährt der App Lesezugriff auf Videodateien in deinem freigegebenen Speicher."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"Lesezugriff auf Bilddateien im freigegebenen Speicher"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Gewährt der App Lesezugriff auf Bilddateien in deinem freigegebenen Speicher."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"Vom Nutzer ausgewählte Bild- und Videodateien aus dem freigegebenen Speicher lesen"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Die App darf Bild- und Videodateien lesen, die du aus dem freigegebenen Speicher auswählst."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"Inhalte deines freigegebenen Speichers ändern oder löschen"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"So kann die App Inhalte deines freigegebenen Speichers erstellen."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP-Anrufe tätigen/empfangen"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALLIEREN"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TROTZDEM ÖFFNEN"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Schädliche App erkannt"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> den Zugriff auf alle Geräteprotokolle erlauben?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Einmaligen Zugriff zulassen"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nicht zulassen"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen, daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugriff auf einige Protokolle oder Informationen auf deinem Gerät."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen. Daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugang zu einigen Protokollen oder Informationen auf deinem Gerät.\n\nWeitere Informationen findest du unter g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nicht mehr anzeigen"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> möchte Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzeigen"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Bearbeiten"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Gerät vibriert bei Anrufen und Benachrichtigungen"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Zugriff auf die Kamera des Smartphones über dein Gerät (<xliff:g id="DEVICE">%1$s</xliff:g>) nicht möglich"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Zugriff auf die Kamera des Tablets über dein Gerät (<xliff:g id="DEVICE">%1$s</xliff:g>) nicht möglich"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Während des Streamings ist kein Zugriff möglich. Versuch es stattdessen auf deinem Smartphone."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Standardeinstellung des Systems"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 8cb3989b..91d8aaf 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Επιτρέπει στην εφαρμογή να δημιουργεί μόνιμα τμήματα του εαυτού της στη μνήμη. Αυτό μπορεί να περιορίζει τη μνήμη που διατίθεται σε άλλες εφαρμογές, καθυστερώντας τη λειτουργία του τηλεφώνου."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"εκτέλεση υπηρεσίας προσκηνίου"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί υπηρεσίες προσκηνίου."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"υπολογίζει τον αποθηκευτικό χώρο εφαρμογής"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Επιτρέπει στην εφαρμογή να ανακτήσει τα μεγέθη κώδικα, δεδομένων και προσωρινής μνήμης"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"τροποποίηση ρυθμίσεων συστήματος"</string>
@@ -447,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Αυτή η εφαρμογή μπορεί να εγγράφει ήχο μέσω του μικροφώνου, όταν τη χρησιμοποιείτε."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"εγγραφή ήχου στο παρασκήνιο"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Αυτή η εφαρμογή μπορεί να εγγράφει ήχο μέσω του μικροφώνου, ανά πάσα στιγμή."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ανίχνευση καταγραφών οθόνης που περιέχουν τα παράθυρα της εφαρμογής"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Η εφαρμογή θα λάβει ειδοποίηση όταν ληφθεί ένα στιγμιότυπο οθόνης ενώ βρίσκεται σε χρήση."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"στέλνει εντολές στην κάρτα SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Επιτρέπει στην εφαρμογή την αποστολή εντολών στην κάρτα SIM. Αυτό είναι εξαιρετικά επικίνδυνο."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"αναγνώριση σωματικής δραστηριότητας"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Επιτρέπει στην εφαρμογή την ανάγνωση αρχείων βίντεο από τον κοινόχρηστο αποθηκευτικό σας χώρο."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ανάγνωση αρχείων εικόνας από τον κοινόχρηστο αποθηκευτικό χώρο"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Επιτρέπει στην εφαρμογή την ανάγνωση αρχείων εικόνας από τον κοινόχρηστο αποθηκευτικό σας χώρο."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ανάγνωση αρχείων εικόνας και βίντεο που έχει επιλέξει ο χρήστης από τον κοινόχρηστο αποθηκευτικό χώρο"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Επιτρέπει στην εφαρμογή την ανάγνωση των αρχείων εικόνας και βίντεο που θα επιλέξετε από τον κοινόχρηστο αποθηκευτικό χώρο."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"τροποποιεί ή διαγράφει το περιεχόμενο του κοινόχρηστου αποθηκευτικού χώρου σας"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Επιτρέπει στην εφαρμογή την εγγραφή του περιεχομένου του κοινόχρηστου αποθηκευτικού χώρου σας."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"πραγματοποιεί/λαμβάνει κλήσεις SIP"</string>
@@ -2048,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ΑΠΕΓΚΑΤΑΣΤΑΣΗ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ΑΝΟΙΓΜΑ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Εντοπίστηκε επιβλαβής εφαρμογή"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Να επιτρέπεται στο <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> η πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής;"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Να επιτρέπεται η πρόσβαση για μία φορά"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Να μην επιτραπεί"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή ορισμένες πληροφορίες στη συσκευή σας."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή σε πληροφορίες στη συσκευή σας.\n\nΜάθετε περισσότερα στη διεύθυνση g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Να μην εμφανισ. ξανά"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Η εφαρμογή <xliff:g id="APP_0">%1$s</xliff:g> θέλει να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Επεξεργασία"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Θα υπάρχει δόνηση για κλήσεις και ειδοποιήσεις"</string>
@@ -2294,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Δεν είναι δυνατή η πρόσβαση στην κάμερα τηλεφώνου από το <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Δεν είναι δυνατή η πρόσβαση στην κάμερα του tablet από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Δεν είναι δυνατή η πρόσβαση σε αυτό το στοιχείο κατά τη ροή. Δοκιμάστε στο τηλέφωνό σας."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Προεπιλογή συστήματος"</string>
<string name="default_card_name" msgid="9198284935962911468">"ΚΑΡΤΑ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 75db3826..8ef72d1 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
@@ -447,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -2048,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
@@ -2294,6 +2340,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 883cd55..b16f3b6 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -28,18 +28,18 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
- <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
- <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
+ <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialing numbers only."</string>
+ <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
<string name="serviceEnabledFor" msgid="1463104778656711613">"Service was enabled for:"</string>
<string name="serviceDisabled" msgid="641878791205871379">"Service has been disabled."</string>
<string name="serviceRegistered" msgid="3856192211729577482">"Registration was successful."</string>
- <string name="serviceErased" msgid="997354043770513494">"Erase successful."</string>
+ <string name="serviceErased" msgid="997354043770513494">"Erasure was successful."</string>
<string name="passwordIncorrect" msgid="917087532676155877">"Incorrect password."</string>
<string name="mmiComplete" msgid="6341884570892520140">"MMI complete."</string>
- <string name="badPin" msgid="888372071306274355">"The old PIN that you typed is incorrect."</string>
- <string name="badPuk" msgid="4232069163733147376">"The PUK that you typed isn\'t correct."</string>
- <string name="mismatchPin" msgid="2929611853228707473">"The PINs that you typed don\'t match."</string>
+ <string name="badPin" msgid="888372071306274355">"The old PIN you typed isn\'t correct."</string>
+ <string name="badPuk" msgid="4232069163733147376">"The PUK you typed isn\'t correct."</string>
+ <string name="mismatchPin" msgid="2929611853228707473">"The PINs you typed don\'t match."</string>
<string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
<string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
<string name="needPuk" msgid="7321876090152422918">"Your SIM card is PUK-locked. Type the PUK code to unlock it."</string>
@@ -52,7 +52,7 @@
<string name="imei" msgid="2157082351232630390">"IMEI"</string>
<string name="meid" msgid="3291227361605924674">"MEID"</string>
<string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string>
- <string name="ClirMmi" msgid="6752346475055446417">"Hide outgoing caller ID"</string>
+ <string name="ClirMmi" msgid="6752346475055446417">"Hide Outgoing Caller ID"</string>
<string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string>
<string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string>
<string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string>
@@ -62,7 +62,7 @@
<string name="PinMmi" msgid="7133542099618330959">"PIN change"</string>
<string name="CnipMmi" msgid="4897531155968151160">"Calling number present"</string>
<string name="CnirMmi" msgid="885292039284503036">"Calling number restricted"</string>
- <string name="ThreeWCMmi" msgid="2436550866139999411">"Three-way calling"</string>
+ <string name="ThreeWCMmi" msgid="2436550866139999411">"Three way calling"</string>
<string name="RuacMmi" msgid="1876047385848991110">"Rejection of undesired annoying calls"</string>
<string name="CndMmi" msgid="185136449405618437">"Calling number delivery"</string>
<string name="DndMmi" msgid="8797375819689129800">"Do not disturb"</string>
@@ -73,7 +73,7 @@
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
- <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this anytime in Settings"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string>
@@ -90,7 +90,7 @@
<string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Mobile data status"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS messages"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Voicemail messages"</string>
- <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi Calling"</string>
+ <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi calling"</string>
<string name="notification_channel_sim" msgid="5098802350325677490">"SIM status"</string>
<string name="notification_channel_sim_high_prio" msgid="642361929452850928">"High priority SIM status"</string>
<string name="peerTtyModeFull" msgid="337553730440832160">"Peer requested TTY Mode FULL"</string>
@@ -108,7 +108,7 @@
<string name="roamingText0" msgid="7793257871609854208">"Roaming Indicator On"</string>
<string name="roamingText1" msgid="5073028598334616445">"Roaming Indicator Off"</string>
<string name="roamingText2" msgid="2834048284153110598">"Roaming Indicator Flashing"</string>
- <string name="roamingText3" msgid="831690234035748988">"Out of local area"</string>
+ <string name="roamingText3" msgid="831690234035748988">"Out of Neighborhood"</string>
<string name="roamingText4" msgid="2171252529065590728">"Out of Building"</string>
<string name="roamingText5" msgid="4294671587635796641">"Roaming - Preferred System"</string>
<string name="roamingText6" msgid="5536156746637992029">"Roaming - Available System"</string>
@@ -129,15 +129,15 @@
<!-- no translation found for wfcSpnFormat_spn (2982505428519096311) -->
<skip />
<string name="wfcSpnFormat_spn_wifi_calling" msgid="3165949348000906194">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi Calling"</string>
- <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi calling"</string>
- <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN call"</string>
+ <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> WiFi Calling"</string>
+ <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN Call"</string>
<string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN Call"</string>
<string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi"</string>
- <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Wi-Fi Calling | <xliff:g id="SPN">%s</xliff:g>"</string>
+ <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"WiFi Calling | <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="wfcSpnFormat_spn_vowifi" msgid="6865214948822061486">"<xliff:g id="SPN">%s</xliff:g> VoWifi"</string>
<string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Wi-Fi Calling"</string>
<string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"Wi-Fi"</string>
- <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Wi-Fi Calling"</string>
+ <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"WiFi Calling"</string>
<string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
<string name="wifi_calling_off_summary" msgid="5626710010766902560">"Off"</string>
<string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Call over Wi-Fi"</string>
@@ -145,7 +145,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi only"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup calling"</string>
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup Calling"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string>
@@ -169,13 +169,13 @@
<string name="httpErrorFile" msgid="3400658466057744084">"Couldn\'t access the file."</string>
<string name="httpErrorFileNotFound" msgid="5191433324871147386">"Couldn\'t find the requested file."</string>
<string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Too many requests are being processed. Try again later."</string>
- <string name="notification_title" msgid="5783748077084481121">"Sign-in error for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
+ <string name="notification_title" msgid="5783748077084481121">"Signin error for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
<string name="contentServiceSync" msgid="2341041749565687871">"Sync"</string>
<string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Can\'t sync"</string>
<string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Attempted to delete too many <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
<string name="low_memory" product="tablet" msgid="5557552311566179924">"Tablet storage is full. Delete some files to free space."</string>
- <string name="low_memory" product="watch" msgid="3479447988234030194">"Watch storage is full. Delete some files to free up space."</string>
- <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV device storage is full. Delete some files to free up space."</string>
+ <string name="low_memory" product="watch" msgid="3479447988234030194">"Watch storage is full. Delete some files to free space."</string>
+ <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV device storage is full. Delete some files to free space."</string>
<string name="low_memory" product="default" msgid="2539532364144025569">"Phone storage is full. Delete some files to free space."</string>
<string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Certificate authority installed}other{Certificate authorities installed}}"</string>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"By an unknown third party"</string>
@@ -189,16 +189,16 @@
<string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Your organization manages this device and may monitor network traffic. Tap for details."</string>
<string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string>
- <string name="location_changed_notification_text" msgid="7158423339982706912">"Contact your IT admin to find out more"</string>
- <string name="geofencing_service" msgid="3826902410740315456">"Geofencing service"</string>
+ <string name="location_changed_notification_text" msgid="7158423339982706912">"Contact your IT admin to learn more"</string>
+ <string name="geofencing_service" msgid="3826902410740315456">"Geofencing Service"</string>
<string name="country_detector" msgid="7023275114706088854">"Country Detector"</string>
<string name="location_service" msgid="2439187616018455546">"Location Service"</string>
- <string name="gnss_service" msgid="8907781262179951385">"GNSS service"</string>
+ <string name="gnss_service" msgid="8907781262179951385">"GNSS Service"</string>
<string name="sensor_notification_service" msgid="7474531979178682676">"Sensor Notification Service"</string>
<string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
- <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS time update service"</string>
- <string name="device_policy_manager_service" msgid="5085762851388850332">"Device Policy manager service"</string>
- <string name="music_recognition_manager_service" msgid="7481956037950276359">"Music recognition manager service"</string>
+ <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Time Update Service"</string>
+ <string name="device_policy_manager_service" msgid="5085762851388850332">"Device Policy Manager Service"</string>
+ <string name="music_recognition_manager_service" msgid="7481956037950276359">"Music Recognition Manager Service"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
<string name="factory_reset_message" msgid="2657049595153992213">"The admin app can\'t be used. Your device will now be erased.\n\nIf you have questions, contact your organization\'s admin."</string>
<string name="printing_disabled_by" msgid="3517499806528864633">"Printing disabled by <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -231,9 +231,9 @@
<string name="shutdown_confirm" product="default" msgid="136816458966692315">"Your phone will shut down."</string>
<string name="shutdown_confirm_question" msgid="796151167261608447">"Do you want to shut down?"</string>
<string name="reboot_safemode_title" msgid="5853949122655346734">"Reboot to safe mode"</string>
- <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Do you want to reboot into safe mode? This will disable all third-party applications that you have installed. They will be restored when you reboot again."</string>
+ <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Do you want to reboot into safe mode? This will disable all third party applications you have installed. They will be restored when you reboot again."</string>
<string name="recent_tasks_title" msgid="8183172372995396653">"Recent"</string>
- <string name="no_recent_tasks" msgid="9063946524312275906">"No recent apps"</string>
+ <string name="no_recent_tasks" msgid="9063946524312275906">"No recent apps."</string>
<string name="global_actions" product="tablet" msgid="4412132498517933867">"Tablet options"</string>
<string name="global_actions" product="tv" msgid="3871763739487450369">"Android TV options"</string>
<string name="global_actions" product="default" msgid="6410072189971495460">"Phone options"</string>
@@ -246,9 +246,9 @@
<string name="global_action_logout" msgid="6093581310002476511">"End session"</string>
<string name="global_action_screenshot" msgid="2610053466156478564">"Screenshot"</string>
<string name="bugreport_title" msgid="8549990811777373050">"Bug report"</string>
- <string name="bugreport_message" msgid="5212529146119624326">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
+ <string name="bugreport_message" msgid="5212529146119624326">"This will collect information about your current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient."</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Interactive report"</string>
- <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Use this under most circumstances. It allows you to track progress of the report, enter more details about the problem and take screenshots. It might omit some less-used sections that take a long time to report."</string>
+ <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Use this under most circumstances. It allows you to track progress of the report, enter more details about the problem, and take screenshots. It might omit some less-used sections that take a long time to report."</string>
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Full report"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Use this option for minimal system interference when your device is unresponsive or too slow, or when you need all report sections. Does not allow you to enter more details or take additional screenshots."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Taking screenshot for bug report in # second.}other{Taking screenshot for bug report in # seconds.}}"</string>
@@ -316,7 +316,7 @@
<string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"Nearby devices"</string>
<string name="permgroupdesc_nearby_devices" msgid="3213561597116913508">"discover and connect to nearby devices"</string>
<string name="permgrouplab_calllog" msgid="7926834372073550288">"Call logs"</string>
- <string name="permgroupdesc_calllog" msgid="2026996642917801803">"read and write phone call logs"</string>
+ <string name="permgroupdesc_calllog" msgid="2026996642917801803">"read and write phone call log"</string>
<string name="permgrouplab_phone" msgid="570318944091926620">"Phone"</string>
<string name="permgroupdesc_phone" msgid="270048070781478204">"make and manage phone calls"</string>
<string name="permgrouplab_sensors" msgid="9134046949784064495">"Body sensors"</string>
@@ -332,7 +332,7 @@
<string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Control display magnification"</string>
<string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Control the display\'s zoom level and positioning."</string>
<string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Perform gestures"</string>
- <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch and perform other gestures."</string>
+ <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch, and perform other gestures."</string>
<string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerprint gestures"</string>
<string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Can capture gestures performed on the device\'s fingerprint sensor."</string>
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string>
@@ -345,24 +345,24 @@
<string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Allows the app to expand or collapse the status bar."</string>
<string name="permlab_fullScreenIntent" msgid="4310888199502509104">"display notifications as full screen activities on a locked device"</string>
<string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Allows the app to display notifications as full screen activities on a locked device"</string>
- <string name="permlab_install_shortcut" msgid="7451554307502256221">"Install shortcuts"</string>
- <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Allows an application to add Home screen shortcuts without user intervention."</string>
+ <string name="permlab_install_shortcut" msgid="7451554307502256221">"install shortcuts"</string>
+ <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Allows an application to add Homescreen shortcuts without user intervention."</string>
<string name="permlab_uninstall_shortcut" msgid="295263654781900390">"uninstall shortcuts"</string>
- <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Allows the application to remove Home screen shortcuts without user intervention."</string>
+ <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Allows the application to remove Homescreen shortcuts without user intervention."</string>
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"reroute outgoing calls"</string>
- <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Allows the app to see the number being dialled during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Allows the app to see the number being dialed during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
<string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"answer phone calls"</string>
<string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Allows the app to answer an incoming phone call."</string>
<string name="permlab_receiveSms" msgid="505961632050451881">"receive text messages (SMS)"</string>
- <string name="permdesc_receiveSms" msgid="1797345626687832285">"Allows the app to receive and process SMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
+ <string name="permdesc_receiveSms" msgid="1797345626687832285">"Allows the app to receive and process SMS messages. This means the app could monitor or delete messages sent to your device without showing them to you."</string>
<string name="permlab_receiveMms" msgid="4000650116674380275">"receive text messages (MMS)"</string>
- <string name="permdesc_receiveMms" msgid="958102423732219710">"Allows the app to receive and process MMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
+ <string name="permdesc_receiveMms" msgid="958102423732219710">"Allows the app to receive and process MMS messages. This means the app could monitor or delete messages sent to your device without showing them to you."</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Forward cell broadcast messages"</string>
<string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
<string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Manage ongoing calls"</string>
<string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Allows an app to see details about ongoing calls on your device and to control these calls."</string>
- <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read mobile broadcast messages"</string>
- <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read mobile broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency mobile broadcast is received."</string>
+ <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read cell broadcast messages"</string>
+ <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read cell broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
<string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"read subscribed feeds"</string>
<string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Allows the app to get details about the currently synced feeds."</string>
<string name="permlab_sendSms" msgid="7757368721742014252">"send and view SMS messages"</string>
@@ -377,7 +377,7 @@
<string name="permdesc_getTasks" msgid="7388138607018233726">"Allows the app to retrieve information about currently and recently running tasks. This may allow the app to discover information about which applications are used on the device."</string>
<string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"manage profile and device owners"</string>
<string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Allows apps to set the profile owners and the device owner."</string>
- <string name="permlab_reorderTasks" msgid="7598562301992923804">"re-order running apps"</string>
+ <string name="permlab_reorderTasks" msgid="7598562301992923804">"reorder running apps"</string>
<string name="permdesc_reorderTasks" msgid="8796089937352344183">"Allows the app to move tasks to the foreground and background. The app may do this without your input."</string>
<string name="permlab_enableCarMode" msgid="893019409519325311">"enable car mode"</string>
<string name="permdesc_enableCarMode" msgid="56419168820473508">"Allows the app to enable the car mode."</string>
@@ -390,13 +390,61 @@
<string name="permlab_useDataInBackground" msgid="783415807623038947">"use data in the background"</string>
<string name="permdesc_useDataInBackground" msgid="1230753883865891987">"This app can use data in the background. This may increase data usage."</string>
<string name="permlab_persistentActivity" msgid="464970041740567970">"make app always run"</string>
- <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the tablet."</string>
- <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps slowing down your Android TV device."</string>
- <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
+ <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the tablet."</string>
+ <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down your Android TV device."</string>
+ <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
- <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
+ <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data, and cache sizes"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
<string name="permdesc_writeSettings" msgid="8293047411196067188">"Allows the app to modify the system\'s settings data. Malicious apps may corrupt your system\'s configuration."</string>
<string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"run at startup"</string>
@@ -408,9 +456,9 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
<string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
<string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
@@ -419,26 +467,26 @@
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
<string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Allows the app to modify your tablet\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
- <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Allows the app to modify your Android TV device\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to delete or modify your call log."</string>
+ <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Allows the app to modify your Android TV device\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
<string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Allows the app to modify your phone\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
<string name="permlab_bodySensors" msgid="662918578601619569">"Access body sensor data, like heart rate, while in use"</string>
- <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Allows the app to access body sensor data, such as heart rate, temperature and blood oxygen percentage, while the app is in use."</string>
+ <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in use."</string>
<string name="permlab_bodySensors_background" msgid="4912560779957760446">"Access body sensor data, like heart rate, while in the background"</string>
- <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Allows the app to access body sensor data, such as heart rate, temperature and blood oxygen percentage, while the app is in the background."</string>
+ <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background."</string>
<string name="permlab_readCalendar" msgid="6408654259475396200">"Read calendar events and details"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"This app can read all calendar events stored on your tablet and share or save your calendar data."</string>
<string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"This app can read all calendar events stored on your Android TV device and share or save your calendar data."</string>
<string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"This app can read all calendar events stored on your phone and share or save your calendar data."</string>
- <string name="permlab_writeCalendar" msgid="6422137308329578076">"add or modify calendar events and send emails to guests without owners\' knowledge"</string>
- <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"This app can add, remove or change calendar events on your tablet. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
- <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"This app can add, remove or change calendar events on your Android TV device. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
- <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"This app can add, remove or change calendar events on your phone. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
+ <string name="permlab_writeCalendar" msgid="6422137308329578076">"add or modify calendar events and send email to guests without owners\' knowledge"</string>
+ <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"This app can add, remove, or change calendar events on your tablet. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
+ <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"This app can add, remove, or change calendar events on your Android TV device. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
+ <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"This app can add, remove, or change calendar events on your phone. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"This app can get your precise location from Location Services while the app is in use. Location Services for your device must be turned on for the app to get location. This may increase battery usage."</string>
+ <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"This app can get your precise location from location services while the app is in use. Location services for your device must be turned on for the app to get location. This may increase battery usage."</string>
<string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
- <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"This app can get your approximate location from Location Services while the app is in use. Location Services for your device must be turned on for the app to get location."</string>
+ <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"This app can get your approximate location from location services while the app is in use. Location services for your device must be turned on for the app to get location."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"This app can access location at any time, even while the app is not in use."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
@@ -447,15 +495,17 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
- <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
- <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string>
+ <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognize physical activity"</string>
+ <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognize your physical activity."</string>
<string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string>
<string name="permdesc_camera" msgid="5240801376168647151">"This app can take pictures and record videos using the camera while the app is in use."</string>
<string name="permlab_backgroundCamera" msgid="7549917926079731681">"take pictures and videos in the background"</string>
<string name="permdesc_backgroundCamera" msgid="1615291686191138250">"This app can take pictures and record videos using the camera at any time."</string>
- <string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string>
+ <string name="permlab_systemCamera" msgid="3642917457796210580">"Allow an application or service access to system cameras to take pictures and videos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string>
<string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"This app can receive callbacks when any camera device is being opened (by what application) or closed."</string>
@@ -467,14 +517,14 @@
<string name="permlab_accessImsCallService" msgid="442192920714863782">"access IMS call service"</string>
<string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Allows the app to use the IMS service to make calls without your intervention."</string>
<string name="permlab_readPhoneState" msgid="8138526903259297969">"read phone status and identity"</string>
- <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Allows the app to access the phone features of the device. This permission allows the app to determine the phone number and device IDs, whether a call is active and the remote number connected by a call."</string>
+ <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Allows the app to access the phone features of the device. This permission allows the app to determine the phone number and device IDs, whether a call is active, and the remote number connected by a call."</string>
<string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"read basic telephony status and identity"</string>
<string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Allows the app to access the basic telephony features of the device."</string>
<string name="permlab_manageOwnCalls" msgid="9033349060307561370">"route calls through the system"</string>
<string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Allows the app to route its calls through the system in order to improve the calling experience."</string>
<string name="permlab_callCompanionApp" msgid="3654373653014126884">"see and control calls through the system."</string>
<string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Allows the app to see and control ongoing calls on the device. This includes information such as call numbers for calls and the state of the calls."</string>
- <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"exempt from audio recording restrictions"</string>
+ <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"exempt from audio record restrictions"</string>
<string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Exempt the app from restrictions to record audio."</string>
<string name="permlab_acceptHandover" msgid="2925523073573116523">"continue a call from another app"</string>
<string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Allows the app to continue a call which was started in another app."</string>
@@ -501,57 +551,57 @@
<string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Allows the app to change your Android TV device\'s time zone."</string>
<string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Allows the app to change the phone\'s time zone."</string>
<string name="permlab_getAccounts" msgid="5304317160463582791">"find accounts on the device"</string>
- <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Allows the app to get the list of accounts known by the tablet. This may include any accounts created by applications that you have installed."</string>
- <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Allows the app to get the list of accounts known by your Android TV device. This may include any accounts created by applications that you have installed."</string>
- <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Allows the app to get the list of accounts known by the phone. This may include any accounts created by applications that you have installed."</string>
+ <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Allows the app to get the list of accounts known by the tablet. This may include any accounts created by applications you have installed."</string>
+ <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Allows the app to get the list of accounts known by your Android TV device. This may include any accounts created by applications you have installed."</string>
+ <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Allows the app to get the list of accounts known by the phone. This may include any accounts created by applications you have installed."</string>
<string name="permlab_accessNetworkState" msgid="2349126720783633918">"view network connections"</string>
<string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Allows the app to view information about network connections such as which networks exist and are connected."</string>
<string name="permlab_createNetworkSockets" msgid="3224420491603590541">"have full network access"</string>
- <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Allows the app to create network sockets and use customised network protocols. The browser and other applications provide means to send data to the Internet, so this permission is not required to send data to the Internet."</string>
+ <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Allows the app to create network sockets and use custom network protocols. The browser and other applications provide means to send data to the internet, so this permission is not required to send data to the internet."</string>
<string name="permlab_changeNetworkState" msgid="8945711637530425586">"change network connectivity"</string>
<string name="permdesc_changeNetworkState" msgid="649341947816898736">"Allows the app to change the state of network connectivity."</string>
<string name="permlab_changeTetherState" msgid="9079611809931863861">"change tethered connectivity"</string>
<string name="permdesc_changeTetherState" msgid="3025129606422533085">"Allows the app to change the state of tethered network connectivity."</string>
<string name="permlab_accessWifiState" msgid="5552488500317911052">"view Wi-Fi connections"</string>
<string name="permdesc_accessWifiState" msgid="6913641669259483363">"Allows the app to view information about Wi-Fi networking, such as whether Wi-Fi is enabled and name of connected Wi-Fi devices."</string>
- <string name="permlab_changeWifiState" msgid="7947824109713181554">"Connect and disconnect from Wi-Fi"</string>
+ <string name="permlab_changeWifiState" msgid="7947824109713181554">"connect and disconnect from Wi-Fi"</string>
<string name="permdesc_changeWifiState" msgid="7170350070554505384">"Allows the app to connect to and disconnect from Wi-Fi access points and to make changes to device configuration for Wi-Fi networks."</string>
<string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"allow Wi-Fi Multicast reception"</string>
<string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your tablet. It uses more power than the non-multicast mode."</string>
<string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your Android TV device. It uses more power than the non-multicast mode."</string>
<string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your phone. It uses more power than the non-multicast mode."</string>
- <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"Access Bluetooth settings"</string>
- <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Allows the app to configure the local Bluetooth tablet and to discover and pair with remote devices."</string>
- <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Allows the app to configure Bluetooth on your Android TV device and to discover and pair with remote devices."</string>
- <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Allows the app to configure the local Bluetooth phone and to discover and pair with remote devices."</string>
+ <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"access Bluetooth settings"</string>
+ <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Allows the app to configure the local Bluetooth tablet, and to discover and pair with remote devices."</string>
+ <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Allows the app to configure Bluetooth on your Android TV device, and to discover and pair with remote devices."</string>
+ <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Allows the app to configure the local Bluetooth phone, and to discover and pair with remote devices."</string>
<string name="permlab_accessWimaxState" msgid="7029563339012437434">"connect and disconnect from WiMAX"</string>
<string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Allows the app to determine whether WiMAX is enabled and information about any WiMAX networks that are connected."</string>
<string name="permlab_changeWimaxState" msgid="6223305780806267462">"change WiMAX state"</string>
<string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Allows the app to connect the tablet to and disconnect the tablet from WiMAX networks."</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Allows the app to connect your Android TV device to and disconnect your Android TV device from WiMAX networks."</string>
<string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Allows the app to connect the phone to and disconnect the phone from WiMAX networks."</string>
- <string name="permlab_bluetooth" msgid="586333280736937209">"Pair with Bluetooth devices"</string>
- <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet and to make and accept connections with paired devices."</string>
- <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device and to make and accept connections with paired devices."</string>
- <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone and to make and accept connections with paired devices."</string>
+ <string name="permlab_bluetooth" msgid="586333280736937209">"pair with Bluetooth devices"</string>
+ <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet, and to make and accept connections with paired devices."</string>
+ <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device, and to make and accept connections with paired devices."</string>
+ <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone, and to make and accept connections with paired devices."</string>
<string name="permlab_bluetooth_scan" msgid="5402587142833124594">"discover and pair nearby Bluetooth devices"</string>
<string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Allows the app to discover and pair nearby Bluetooth devices"</string>
<string name="permlab_bluetooth_connect" msgid="6657463246355003528">"connect to paired Bluetooth devices"</string>
<string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Allows the app to connect to paired Bluetooth devices"</string>
<string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"advertise to nearby Bluetooth devices"</string>
<string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Allows the app to advertise to nearby Bluetooth devices"</string>
- <string name="permlab_uwb_ranging" msgid="8141915781475770665">"determine relative position between nearby ultra-wideband devices"</string>
- <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Allow the app to determine relative position between nearby ultra-wideband devices"</string>
+ <string name="permlab_uwb_ranging" msgid="8141915781475770665">"determine relative position between nearby Ultra-Wideband devices"</string>
+ <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Allow the app to determine relative position between nearby Ultra-Wideband devices"</string>
<string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"interact with nearby Wi‑Fi devices"</string>
- <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Allows the app to advertise, connect and determine the relative position of nearby Wi‑Fi devices"</string>
- <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Preferred NFC payment service information"</string>
- <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred NFC payment service information, such as registered aids and route destination."</string>
- <string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
- <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
+ <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Allows the app to advertise, connect, and determine the relative position of nearby Wi‑Fi devices"</string>
+ <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Preferred NFC Payment Service Information"</string>
+ <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred nfc payment service information like registered aids and route destination."</string>
+ <string name="permlab_nfc" msgid="1904455246837674977">"control Near Field Communication"</string>
+ <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards, and readers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
<string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished."</string>
<string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"request screen lock complexity"</string>
- <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Allows the app to learn the screen lock complexity level (high, medium, low or none), which indicates the possible range of length and type of the screen lock. The app can also suggest to users that they update the screen lock to a certain level but users can freely ignore and navigate away. Note that the screen lock is not stored in plain text so the app does not know the exact password."</string>
+ <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Allows the app to learn the screen lock complexity level (high, medium, low or none), which indicates the possible range of length and type of the screen lock. The app can also suggest to users that they update the screen lock to a certain level but users can freely ignore and navigate away. Note that the screen lock is not stored in plaintext so the app does not know the exact password."</string>
<string name="permlab_postNotification" msgid="4875401198597803658">"show notifications"</string>
<string name="permdesc_postNotification" msgid="5974977162462877075">"Allows the app to show notifications"</string>
<string name="permlab_turnScreenOn" msgid="219344053664171492">"turn on the screen"</string>
@@ -560,7 +610,7 @@
<string name="permdesc_useBiometric" msgid="7502858732677143410">"Allows the app to use biometric hardware for authentication"</string>
<string name="permlab_manageFingerprint" msgid="7432667156322821178">"manage fingerprint hardware"</string>
<string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Allows the app to invoke methods to add and delete fingerprint templates for use."</string>
- <string name="permlab_useFingerprint" msgid="1001421069766751922">"Use fingerprint hardware"</string>
+ <string name="permlab_useFingerprint" msgid="1001421069766751922">"use fingerprint hardware"</string>
<string name="permdesc_useFingerprint" msgid="412463055059323742">"Allows the app to use fingerprint hardware for authentication"</string>
<string name="permlab_audioWrite" msgid="8501705294265669405">"modify your music collection"</string>
<string name="permdesc_audioWrite" msgid="8057399517013412431">"Allows the app to modify your music collection."</string>
@@ -572,19 +622,19 @@
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Allows the app to read locations from your media collection."</string>
<string name="biometric_app_setting_name" msgid="3339209978734534457">"Use biometrics"</string>
<string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Use biometrics or screen lock"</string>
- <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify that it’s you"</string>
+ <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify it’s you"</string>
<string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use your biometric to continue"</string>
<string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use your biometric or screen lock to continue"</string>
<string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
- <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
- <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
- <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
- <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
- <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
+ <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication canceled"</string>
+ <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognized"</string>
+ <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication canceled"</string>
+ <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern, or password set"</string>
+ <string name="biometric_error_generic" msgid="6784371929985434439">"Error authenticating"</string>
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+ <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognize fingerprint. Try again."</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -596,16 +646,16 @@
<string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Change the position of your finger slightly each time"</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
- <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Fingerprint not recognised"</string>
- <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Fingerprint not recognised"</string>
+ <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Fingerprint not recognized"</string>
+ <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Fingerprint not recognized"</string>
<string name="fingerprint_authenticated" msgid="2024862866860283100">"Fingerprint authenticated"</string>
<string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Face authenticated"</string>
- <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Face authenticated. Please press confirm"</string>
+ <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Face authenticated, please press confirm"</string>
<string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Fingerprint hardware not available."</string>
<string name="fingerprint_error_no_space" msgid="7285481581905967580">"Can’t set up fingerprint"</string>
<string name="fingerprint_error_timeout" msgid="7361192266621252164">"Fingerprint setup timed out. Try again."</string>
- <string name="fingerprint_error_canceled" msgid="540026881380070750">"Fingerprint operation cancelled."</string>
- <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingerprint operation cancelled by user."</string>
+ <string name="fingerprint_error_canceled" msgid="540026881380070750">"Fingerprint operation canceled."</string>
+ <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingerprint operation canceled by user."</string>
<string name="fingerprint_error_lockout" msgid="6626753679019351368">"Too many attempts. Use screen lock instead."</string>
<string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"Too many attempts. Use screen lock instead."</string>
<string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"Can’t process fingerprint. Try again."</string>
@@ -637,7 +687,7 @@
<string name="face_acquired_insufficient" msgid="6889245852748492218">"Can’t create your face model. Try again."</string>
<string name="face_acquired_too_bright" msgid="8070756048978079164">"Too bright. Try gentler lighting."</string>
<string name="face_acquired_too_dark" msgid="8539853432479385326">"Not enough light"</string>
- <string name="face_acquired_too_close" msgid="4453646176196302462">"Move phone further away"</string>
+ <string name="face_acquired_too_close" msgid="4453646176196302462">"Move phone farther away"</string>
<string name="face_acquired_too_far" msgid="2922278214231064859">"Move phone closer"</string>
<string name="face_acquired_too_high" msgid="8278815780046368576">"Move phone higher"</string>
<string name="face_acquired_too_low" msgid="4075391872960840081">"Move phone lower"</string>
@@ -647,7 +697,7 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+ <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognize face. Try again."</string>
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
@@ -666,8 +716,8 @@
<string name="face_error_hw_not_available" msgid="5085202213036026288">"Can’t verify face. Hardware not available."</string>
<string name="face_error_timeout" msgid="2598544068593889762">"Try Face Unlock again"</string>
<string name="face_error_no_space" msgid="5649264057026021723">"Can’t store new face data. Delete an old one first."</string>
- <string name="face_error_canceled" msgid="2164434737103802131">"Face operation cancelled."</string>
- <string name="face_error_user_canceled" msgid="5766472033202928373">"Face Unlock cancelled by user"</string>
+ <string name="face_error_canceled" msgid="2164434737103802131">"Face operation canceled."</string>
+ <string name="face_error_user_canceled" msgid="5766472033202928373">"Face Unlock canceled by user"</string>
<string name="face_error_lockout" msgid="7864408714994529437">"Too many attempts. Try again later."</string>
<string name="face_error_lockout_permanent" msgid="3277134834042995260">"Too many attempts. Face Unlock disabled."</string>
<string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Too many attempts. Enter screen lock instead."</string>
@@ -687,7 +737,7 @@
<string name="permlab_readSyncSettings" msgid="6250532864893156277">"read sync settings"</string>
<string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Allows the app to read the sync settings for an account. For example, this can determine whether the People app is synced with an account."</string>
<string name="permlab_writeSyncSettings" msgid="6583154300780427399">"toggle sync on and off"</string>
- <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Allows an app to modify the sync settings for an account. For example, this can be used to enable syncing of the People app with an account."</string>
+ <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Allows an app to modify the sync settings for an account. For example, this can be used to enable sync of the People app with an account."</string>
<string name="permlab_readSyncStats" msgid="3747407238320105332">"read sync statistics"</string>
<string name="permdesc_readSyncStats" msgid="3867809926567379434">"Allows an app to read the sync stats for an account, including the history of sync events and how much data is synced."</string>
<string name="permlab_sdcardRead" msgid="5791467020950064920">"read the contents of your shared storage"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -757,12 +809,12 @@
<string name="policylab_limitPassword" msgid="4851829918814422199">"Set password rules"</string>
<string name="policydesc_limitPassword" msgid="4105491021115793793">"Control the length and the characters allowed in screen lock passwords and PINs."</string>
<string name="policylab_watchLogin" msgid="7599669460083719504">"Monitor screen unlock attempts"</string>
- <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitor the number of incorrect passwords typed when unlocking the screen and lock the tablet or erase all the tablet\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or delete all of your Android TV device\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the infotainment system or erase all the infotainment system\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitor the number of incorrect passwords typed when unlocking the screen and lock the phone or erase all the phone\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the tablet or erase all the tablet\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or erase all your Android TV device\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitor the number of incorrect passwords typed. when unlocking the screen, and lock the infotainment system or erase all the infotainment system\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitor the number of incorrect passwords typed. when unlocking the screen, and lock the phone or erase all the phone\'s data if too many incorrect passwords are typed."</string>
<string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the tablet or erase all this user\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or delete all of this user\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or erase all this user\'s data if too many incorrect passwords are typed."</string>
<string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the infotainment system or erase all this profile\'s data if too many incorrect passwords are typed."</string>
<string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the phone or erase all this user\'s data if too many incorrect passwords are typed."</string>
<string name="policylab_resetPassword" msgid="214556238645096520">"Change the screen lock"</string>
@@ -771,19 +823,19 @@
<string name="policydesc_forceLock" msgid="1008844760853899693">"Control how and when the screen locks."</string>
<string name="policylab_wipeData" msgid="1359485247727537311">"Erase all data"</string>
<string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Erase the tablet\'s data without warning by performing a factory data reset."</string>
- <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Delete your Android TV device\'s data without warning by performing a factory data reset."</string>
+ <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Erase your Android TV device\'s data without warning by performing a factory data reset."</string>
<string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Erase the infotainment system\'s data without warning by performing a factory data reset."</string>
<string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Erase the phone\'s data without warning by performing a factory data reset."</string>
<string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Erase profile data"</string>
<string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Erase user data"</string>
<string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Erase this user\'s data on this tablet without warning."</string>
- <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Delete this user\'s data on this Android TV device without warning."</string>
+ <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Erase this user\'s data on this Android TV device without warning."</string>
<string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Erase this profile\'s data on this infotainment system without warning."</string>
<string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Erase this user\'s data on this phone without warning."</string>
<string name="policylab_setGlobalProxy" msgid="215332221188670221">"Set the device global proxy"</string>
<string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Set the device global proxy to be used while policy is enabled. Only the device owner can set the global proxy."</string>
- <string name="policylab_expirePassword" msgid="6015404400532459169">"Set screen lock password expiry"</string>
- <string name="policydesc_expirePassword" msgid="9136524319325960675">"Change how frequently the screen lock password, PIN or pattern must be changed."</string>
+ <string name="policylab_expirePassword" msgid="6015404400532459169">"Set screen lock password expiration"</string>
+ <string name="policydesc_expirePassword" msgid="9136524319325960675">"Change how frequently the screen lock password, PIN, or pattern must be changed."</string>
<string name="policylab_encryptedStorage" msgid="9012936958126670110">"Set storage encryption"</string>
<string name="policydesc_encryptedStorage" msgid="1102516950740375617">"Require that stored app data be encrypted."</string>
<string name="policylab_disableCamera" msgid="5749486347810162018">"Disable cameras"</string>
@@ -794,8 +846,8 @@
<item msgid="8996339953292723951">"Home"</item>
<item msgid="7740243458912727194">"Mobile"</item>
<item msgid="8526146065496663766">"Work"</item>
- <item msgid="8150904584178569699">"Work fax"</item>
- <item msgid="4537253139152229577">"Home fax"</item>
+ <item msgid="8150904584178569699">"Work Fax"</item>
+ <item msgid="4537253139152229577">"Home Fax"</item>
<item msgid="6751245029698664340">"Pager"</item>
<item msgid="1692790665884224905">"Other"</item>
<item msgid="6216981255272016212">"Custom"</item>
@@ -837,8 +889,8 @@
<string name="phoneTypeHome" msgid="3880132427643623588">"Home"</string>
<string name="phoneTypeMobile" msgid="1178852541462086735">"Mobile"</string>
<string name="phoneTypeWork" msgid="6604967163358864607">"Work"</string>
- <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Work fax"</string>
- <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Home fax"</string>
+ <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Work Fax"</string>
+ <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Home Fax"</string>
<string name="phoneTypePager" msgid="576402072263522767">"Pager"</string>
<string name="phoneTypeOther" msgid="6918196243648754715">"Other"</string>
<string name="phoneTypeCallback" msgid="3455781500844157767">"Callback"</string>
@@ -849,7 +901,7 @@
<string name="phoneTypeOtherFax" msgid="3037145630364770357">"Other Fax"</string>
<string name="phoneTypeRadio" msgid="2637819130239264771">"Radio"</string>
<string name="phoneTypeTelex" msgid="2558783611711876562">"Telex"</string>
- <string name="phoneTypeTtyTdd" msgid="532038552105328779">"TTY/TDD"</string>
+ <string name="phoneTypeTtyTdd" msgid="532038552105328779">"TTY TDD"</string>
<string name="phoneTypeWorkMobile" msgid="7522314392003565121">"Work Mobile"</string>
<string name="phoneTypeWorkPager" msgid="3748332310638505234">"Work Pager"</string>
<string name="phoneTypeAssistant" msgid="757550783842231039">"Assistant"</string>
@@ -880,7 +932,7 @@
<string name="imProtocolGoogleTalk" msgid="9194016024343166782">"Hangouts"</string>
<string name="imProtocolIcq" msgid="2410325380427389521">"ICQ"</string>
<string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
- <string name="imProtocolNetMeeting" msgid="4985002408136148256">"Net Meeting"</string>
+ <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
<string name="orgTypeWork" msgid="8684458700669564172">"Work"</string>
<string name="orgTypeOther" msgid="5450675258408005553">"Other"</string>
<string name="orgTypeCustom" msgid="1126322047677329218">"Custom"</string>
@@ -907,12 +959,12 @@
<string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"Type PIN code"</string>
<string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Type PUK and new PIN code"</string>
<string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"PUK code"</string>
- <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"New PIN Code"</string>
+ <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"New PIN code"</string>
<string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Tap to type password"</font></string>
<string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"Type password to unlock"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Type PIN to unlock"</string>
<string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Incorrect PIN code."</string>
- <string name="keyguard_label_text" msgid="3841953694564168384">"To unlock, press Menu, then 0."</string>
+ <string name="keyguard_label_text" msgid="3841953694564168384">"To unlock, press Menu then 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Emergency number"</string>
<string name="lockscreen_carrier_default" msgid="6192313772955399160">"No service"</string>
<string name="lockscreen_screen_locked" msgid="7364905540516041817">"Screen locked."</string>
@@ -940,7 +992,7 @@
<string name="lockscreen_transport_play_description" msgid="106868788691652733">"Play"</string>
<string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Stop"</string>
<string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Rewind"</string>
- <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast-forward"</string>
+ <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast forward"</string>
<string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"SIM card is PUK-locked."</string>
@@ -950,9 +1002,9 @@
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using your Google sign-in.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using your Google sign-in.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google sign-in.\n\n Please try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, the tablet will be reset to factory default and all user data will be lost."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, your Android TV device will be reset to factory default and all user data will be lost."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, the phone will be reset to factory default and all user data will be lost."</string>
@@ -960,15 +1012,15 @@
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER">%d</xliff:g> times. Your Android TV device will now be reset to factory default."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER">%d</xliff:g> times. The phone will now be reset to factory default."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds."</string>
- <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Forgotten pattern?"</string>
+ <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Forgot pattern?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Account unlock"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Too many pattern attempts"</string>
- <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"To unlock, sign in with your Google Account."</string>
+ <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"To unlock, sign in with your Google account."</string>
<string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Username (email)"</string>
<string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Password"</string>
<string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Sign in"</string>
<string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Invalid username or password."</string>
- <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Forgot your username or password?\nVisit "<b>"google.co.uk/accounts/recovery"</b>"."</string>
+ <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Forgot your username or password?\nVisit "<b>"google.com/accounts/recovery"</b>"."</string>
<string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Checking…"</string>
<string name="lockscreen_unlock_label" msgid="4648257878373307582">"Unlock"</string>
<string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Sound on"</string>
@@ -980,7 +1032,7 @@
<string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"Pattern completed"</string>
<string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"Pattern area."</string>
<string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s. Widget %2$d of %3$d."</string>
- <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Add widget"</string>
+ <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Add widget."</string>
<string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"Empty"</string>
<string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"Unlock area expanded."</string>
<string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Unlock area collapsed."</string>
@@ -997,8 +1049,8 @@
<string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Pattern unlock."</string>
<string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Face Unlock."</string>
<string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"Pin unlock."</string>
- <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"SIM PIN unlock."</string>
- <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"SIM PUK unlock."</string>
+ <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"Sim Pin unlock."</string>
+ <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"Sim Puk unlock."</string>
<string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"Password unlock."</string>
<string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"Pattern area."</string>
<string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"Slide area."</string>
@@ -1020,18 +1072,18 @@
<string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Stay on this Page"</string>
<string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nAre you sure you want to navigate away from this page?"</string>
<string name="save_password_label" msgid="9161712335355510035">"Confirm"</string>
- <string name="double_tap_toast" msgid="7065519579174882778">"Tip: double-tap to zoom in and out."</string>
- <string name="autofill_this_form" msgid="3187132440451621492">"Auto-fill"</string>
- <string name="setup_autofill" msgid="5431369130866618567">"Set up Auto-fill"</string>
+ <string name="double_tap_toast" msgid="7065519579174882778">"Tip: Double-tap to zoom in and out."</string>
+ <string name="autofill_this_form" msgid="3187132440451621492">"Autofill"</string>
+ <string name="setup_autofill" msgid="5431369130866618567">"Set up Autofill"</string>
<string name="autofill_window_title" msgid="4379134104008111961">"Autofill with <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
<string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string>
<string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string>
<string name="autofill_address_summary_format" msgid="8417010069362125194">"$1$2$3"</string>
<string name="autofill_province" msgid="3676846437741893159">"Province"</string>
- <string name="autofill_postal_code" msgid="7034789388968295591">"Postcode"</string>
+ <string name="autofill_postal_code" msgid="7034789388968295591">"Postal code"</string>
<string name="autofill_state" msgid="3341725337190434069">"State"</string>
- <string name="autofill_zip_code" msgid="1315503730274962450">"Zip code"</string>
+ <string name="autofill_zip_code" msgid="1315503730274962450">"ZIP code"</string>
<string name="autofill_county" msgid="7781382735643492173">"County"</string>
<string name="autofill_island" msgid="5367139008536593734">"Island"</string>
<string name="autofill_district" msgid="6428712062213557327">"District"</string>
@@ -1043,15 +1095,15 @@
<string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"read your Web bookmarks and history"</string>
<string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Allows the app to read the history of all URLs that the Browser has visited, and all of the Browser\'s bookmarks. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
<string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"write web bookmarks and history"</string>
- <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Allows the app to modify the Browser\'s history or bookmarks stored on your tablet. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
- <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Allows the app to modify the browser\'s history or bookmarks stored on your Android TV device. This may allow the app to delete or modify browser data. Note: This permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
- <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Allows the app to modify the Browser\'s history or bookmarks stored on your tablet. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Allows the app to modify the Browser\'s history or bookmarks stored on your Android TV device. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
<string name="permlab_setAlarm" msgid="1158001610254173567">"set an alarm"</string>
<string name="permdesc_setAlarm" msgid="2185033720060109640">"Allows the app to set an alarm in an installed alarm clock app. Some alarm clock apps may not implement this feature."</string>
<string name="permlab_addVoicemail" msgid="4770245808840814471">"add voicemail"</string>
<string name="permdesc_addVoicemail" msgid="5470312139820074324">"Allows the app to add messages to your voicemail inbox."</string>
- <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"Modify Browser geo-location permissions"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Allows the app to modify the Browser\'s geo-location permissions. Malicious apps may use this to allow sending location information to arbitrary websites."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"modify Browser geolocation permissions"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Allows the app to modify the Browser\'s geolocation permissions. Malicious apps may use this to allow sending location information to arbitrary web sites."</string>
<string name="save_password_message" msgid="2146409467245462965">"Do you want the browser to remember this password?"</string>
<string name="save_password_notnow" msgid="2878327088951240061">"Not now"</string>
<string name="save_password_remember" msgid="6490888932657708341">"Remember"</string>
@@ -1060,9 +1112,9 @@
<string name="text_copied" msgid="2531420577879738860">"Text copied to clipboard."</string>
<string name="pasted_from_app" msgid="5627698450808256545">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted from <xliff:g id="SOURCE_APP_NAME">%2$s</xliff:g>"</string>
<string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted from your clipboard"</string>
- <string name="pasted_text" msgid="4298871641549173733">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted text that you copied"</string>
- <string name="pasted_image" msgid="4729097394781491022">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted an image that you copied"</string>
- <string name="pasted_content" msgid="646276353060777131">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted content that you copied"</string>
+ <string name="pasted_text" msgid="4298871641549173733">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted text you copied"</string>
+ <string name="pasted_image" msgid="4729097394781491022">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted an image you copied"</string>
+ <string name="pasted_content" msgid="646276353060777131">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted content you copied"</string>
<string name="more_item_label" msgid="7419249600215749115">"More"</string>
<string name="prepend_shortcut_label" msgid="1743716737502867951">"Menu+"</string>
<string name="menu_meta_shortcut_label" msgid="1623390163674762478">"Meta+"</string>
@@ -1091,7 +1143,7 @@
<string name="older" msgid="1645159827884647400">"Older"</string>
<string name="preposition_for_date" msgid="2780767868832729599">"on <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="preposition_for_time" msgid="4336835286453822053">"at <xliff:g id="TIME">%s</xliff:g>"</string>
- <string name="preposition_for_year" msgid="3149809685340130039">"in<xliff:g id="YEAR">%s</xliff:g>"</string>
+ <string name="preposition_for_year" msgid="3149809685340130039">"in <xliff:g id="YEAR">%s</xliff:g>"</string>
<string name="day" msgid="8394717255950176156">"day"</string>
<string name="days" msgid="4570879797423034973">"days"</string>
<string name="hour" msgid="7796325297097314653">"hour"</string>
@@ -1112,7 +1164,7 @@
<string name="duration_minutes_shortest_future" msgid="5260857299282734759">"in <xliff:g id="COUNT">%d</xliff:g>m"</string>
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g>d"</string>
- <string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> y"</string>
+ <string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g>y"</string>
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minute ago}other{# minutes ago}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hour ago}other{# hours ago}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# day ago}other{# days ago}}"</string>
@@ -1138,13 +1190,13 @@
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Failed to copy to clipboard"</string>
<string name="paste" msgid="461843306215520225">"Paste"</string>
<string name="paste_as_plain_text" msgid="7664800665823182587">"Paste as plain text"</string>
- <string name="replace" msgid="7842675434546657444">"Replace..."</string>
+ <string name="replace" msgid="7842675434546657444">"Replace…"</string>
<string name="delete" msgid="1514113991712129054">"Delete"</string>
<string name="copyUrl" msgid="6229645005987260230">"Copy URL"</string>
<string name="selectTextMode" msgid="3225108910999318778">"Select text"</string>
<string name="undo" msgid="3175318090002654673">"Undo"</string>
<string name="redo" msgid="7231448494008532233">"Redo"</string>
- <string name="autofill" msgid="511224882647795296">"Auto-fill"</string>
+ <string name="autofill" msgid="511224882647795296">"Autofill"</string>
<string name="textSelectionCABTitle" msgid="5151441579532476940">"Text selection"</string>
<string name="addToDictionary" msgid="8041821113480950096">"Add to dictionary"</string>
<string name="deleteText" msgid="4200807474529938112">"Delete"</string>
@@ -1154,7 +1206,7 @@
<string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
- <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
+ <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure you have 250MB of free space and restart."</string>
<string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> is running"</string>
<string name="app_running_notification_text" msgid="5120815883400228566">"Tap for more information or to stop the app."</string>
<string name="ok" msgid="2646370155170753815">"OK"</string>
@@ -1170,7 +1222,7 @@
<string name="selected" msgid="6614607926197755875">"selected"</string>
<string name="not_selected" msgid="410652016565864475">"not selected"</string>
<string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{One star out of {max}}other{# stars out of {max}}}"</string>
- <string name="in_progress" msgid="2149208189184319441">"In progress"</string>
+ <string name="in_progress" msgid="2149208189184319441">"in progress"</string>
<string name="whichApplication" msgid="5432266899591255759">"Complete action using"</string>
<string name="whichApplicationNamed" msgid="6969946041713975681">"Complete action using %1$s"</string>
<string name="whichApplicationLabel" msgid="7852182961472531728">"Complete action"</string>
@@ -1233,8 +1285,8 @@
<string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> was built for an incompatible version of the Android OS and may behave unexpectedly. An updated version of the app may be available."</string>
<string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Always show"</string>
<string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Check for update"</string>
- <string name="smv_application" msgid="3775183542777792638">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced Strict Mode policy."</string>
- <string name="smv_process" msgid="1398801497130695446">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has violated its self-enforced StrictMode policy."</string>
+ <string name="smv_application" msgid="3775183542777792638">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced StrictMode policy."</string>
+ <string name="smv_process" msgid="1398801497130695446">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has has violated its self-enforced StrictMode policy."</string>
<string name="android_upgrading_title" product="default" msgid="7279077384220829683">"Phone is updating…"</string>
<string name="android_upgrading_title" product="tablet" msgid="4268417249079938805">"Tablet is updating…"</string>
<string name="android_upgrading_title" product="device" msgid="6774767702998149762">"Device is updating…"</string>
@@ -1242,18 +1294,18 @@
<string name="android_start_title" product="automotive" msgid="7917984412828168079">"Android is starting…"</string>
<string name="android_start_title" product="tablet" msgid="4429767260263190344">"Tablet is starting…"</string>
<string name="android_start_title" product="device" msgid="6967413819673299309">"Device is starting…"</string>
- <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Optimising storage."</string>
+ <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Optimizing storage."</string>
<string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"Finishing system update…"</string>
<string name="app_upgrading_toast" msgid="1016267296049455585">"<xliff:g id="APPLICATION">%1$s</xliff:g> is upgrading…"</string>
- <string name="android_upgrading_apk" msgid="1339564803894466737">"Optimising app <xliff:g id="NUMBER_0">%1$d</xliff:g> of <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
+ <string name="android_upgrading_apk" msgid="1339564803894466737">"Optimizing app <xliff:g id="NUMBER_0">%1$d</xliff:g> of <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
<string name="android_preparing_apk" msgid="589736917792300956">"Preparing <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Starting apps."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Finishing boot."</string>
- <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"You pressed the power button – this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint."</string>
+ <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint."</string>
<string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"To end setup, turn off screen"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Turn off"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continue verifying your fingerprint?"</string>
- <string name="fp_power_button_bp_message" msgid="2983163038168903393">"You pressed the power button – this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint."</string>
+ <string name="fp_power_button_bp_message" msgid="2983163038168903393">"You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint."</string>
<string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Turn off screen"</string>
<string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continue"</string>
<string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
@@ -1268,8 +1320,8 @@
<string name="dump_heap_notification_detail" msgid="8431586843001054050">"Heap dump collected. Tap to share."</string>
<string name="dump_heap_title" msgid="4367128917229233901">"Share heap dump?"</string>
<string name="dump_heap_text" msgid="1692649033835719336">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share with its developer. Be careful: this heap dump can contain any of your personal information that the application has access to."</string>
- <string name="dump_heap_system_text" msgid="6805155514925350849">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share. Be careful: this heap dump can contain any sensitive personal information that the process has access to, which may include things that you’ve typed."</string>
- <string name="dump_heap_ready_text" msgid="5849618132123045516">"A heap dump of <xliff:g id="PROC">%1$s</xliff:g>’s process is available for you to share. Be careful: this heap dump may contain any sensitive personal information that the process has access to, which may include things that you’ve typed."</string>
+ <string name="dump_heap_system_text" msgid="6805155514925350849">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share. Be careful: this heap dump can contain any sensitive personal information that the process has access to, which may include things you’ve typed."</string>
+ <string name="dump_heap_ready_text" msgid="5849618132123045516">"A heap dump of <xliff:g id="PROC">%1$s</xliff:g>’s process is available for you to share. Be careful: this heap dump may contain any sensitive personal information that the process has access to, which may include things you’ve typed."</string>
<string name="sendText" msgid="493003724401350724">"Choose an action for text"</string>
<string name="volume_ringtone" msgid="134784084629229029">"Ringer volume"</string>
<string name="volume_music" msgid="7727274216734955095">"Media volume"</string>
@@ -1289,22 +1341,22 @@
<string name="ringtone_default_with_actual" msgid="2709686194556159773">"Default (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="397111123930141876">"None"</string>
<string name="ringtone_picker_title" msgid="667342618626068253">"Ringtones"</string>
- <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarm Sounds"</string>
- <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Notification Sounds"</string>
+ <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarm sounds"</string>
+ <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Notification sounds"</string>
<string name="ringtone_unknown" msgid="5059495249862816475">"Unknown"</string>
- <string name="wifi_available_sign_in" msgid="381054692557675237">"Sign in to a Wi-Fi network"</string>
+ <string name="wifi_available_sign_in" msgid="381054692557675237">"Sign in to Wi-Fi network"</string>
<string name="network_available_sign_in" msgid="1520342291829283114">"Sign in to network"</string>
<!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
<skip />
- <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+ <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no internet access"</string>
<string name="wifi_no_internet_detailed" msgid="634938444133558942">"Tap for options"</string>
- <string name="mobile_no_internet" msgid="4014455157529909781">"Mobile network has no Internet access"</string>
- <string name="other_networks_no_internet" msgid="6698711684200067033">"Network has no Internet access"</string>
+ <string name="mobile_no_internet" msgid="4014455157529909781">"Mobile network has no internet access"</string>
+ <string name="other_networks_no_internet" msgid="6698711684200067033">"Network has no internet access"</string>
<string name="private_dns_broken_detailed" msgid="3709388271074611847">"Private DNS server cannot be accessed"</string>
<string name="network_partial_connectivity" msgid="4791024923851432291">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
<string name="network_partial_connectivity_detailed" msgid="5741329444564575840">"Tap to connect anyway"</string>
<string name="network_switch_metered" msgid="1531869544142283384">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="1358296010128405906">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+ <string name="network_switch_metered_detail" msgid="1358296010128405906">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no internet access. Charges may apply."</string>
<string name="network_switch_metered_toast" msgid="501662047275723743">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
<item msgid="2255670471736226365">"mobile data"</item>
@@ -1348,7 +1400,7 @@
<string name="date_time_done" msgid="8363155889402873463">"Done"</string>
<string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"NEW: "</font></string>
<string name="perms_description_app" msgid="2747752389870161996">"Provided by <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
- <string name="no_permissions" msgid="5729199278862516390">"No permission required"</string>
+ <string name="no_permissions" msgid="5729199278862516390">"No permissions required"</string>
<string name="perm_costs_money" msgid="749054595022779685">"this may cost you money"</string>
<string name="dlg_ok" msgid="5103447663504839312">"OK"</string>
<string name="usb_charging_notification_title" msgid="1674124518282666955">"Charging this device via USB"</string>
@@ -1360,7 +1412,7 @@
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB accessory connected"</string>
<string name="usb_notification_message" msgid="4715163067192110676">"Tap for more options."</string>
<string name="usb_power_notification_message" msgid="7284765627437897702">"Charging connected device. Tap for more options."</string>
- <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Analogue audio accessory detected"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Analog audio accessory detected"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"The attached device is not compatible with this phone. Tap to learn more."</string>
<string name="adb_active_notification_title" msgid="408390247354560331">"USB debugging connected"</string>
<string name="adb_active_notification_message" msgid="5617264033476778211">"Tap to turn off USB debugging"</string>
@@ -1398,7 +1450,7 @@
<string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Turn off"</string>
<string name="ext_media_checking_notification_title" msgid="8299199995416510094">"Checking <xliff:g id="NAME">%s</xliff:g>…"</string>
<string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Reviewing current content"</string>
- <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analysing media storage"</string>
+ <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analyzing media storage"</string>
<string name="ext_media_new_notification_title" msgid="3517407571407687677">"New <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> isn’t working"</string>
<string name="ext_media_new_notification_message" msgid="6095403121990786986">"Tap to set up"</string>
@@ -1413,7 +1465,7 @@
<string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"You may need to reformat the device. Tap to eject."</string>
<string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"<xliff:g id="NAME">%s</xliff:g> detected"</string>
<string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> isn’t working"</string>
- <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Tap to set up."</string>
+ <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Tap to set up ."</string>
<string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Select to set up <xliff:g id="NAME">%s</xliff:g> in a supported format."</string>
<string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"You may need to reformat the device"</string>
<string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"<xliff:g id="NAME">%s</xliff:g> unexpectedly removed"</string>
@@ -1422,7 +1474,7 @@
<string name="ext_media_nomedia_notification_message" msgid="2832724384636625852">"Some functionality may not work properly. Insert new storage."</string>
<string name="ext_media_unmounting_notification_title" msgid="4147986383917892162">"Ejecting <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"Don’t remove"</string>
- <string name="ext_media_init_action" msgid="2312974060585056709">"Set-up"</string>
+ <string name="ext_media_init_action" msgid="2312974060585056709">"Set up"</string>
<string name="ext_media_unmount_action" msgid="966992232088442745">"Eject"</string>
<string name="ext_media_browse_action" msgid="344865351947079139">"Explore"</string>
<string name="ext_media_seamless_action" msgid="8837030226009268080">"Switch output"</string>
@@ -1430,7 +1482,7 @@
<string name="ext_media_missing_message" msgid="4408988706227922909">"Insert device again"</string>
<string name="ext_media_move_specific_title" msgid="8492118544775964250">"Moving <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_move_title" msgid="2682741525619033637">"Moving data"</string>
- <string name="ext_media_move_success_title" msgid="4901763082647316767">"Content transfer is finished"</string>
+ <string name="ext_media_move_success_title" msgid="4901763082647316767">"Content transfer is done"</string>
<string name="ext_media_move_success_message" msgid="9159542002276982979">"Content moved to <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_move_failure_title" msgid="3184577479181333665">"Couldn’t move content"</string>
<string name="ext_media_move_failure_message" msgid="4197306718121869335">"Try moving content again"</string>
@@ -1454,8 +1506,8 @@
<string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"Allows an application to request installation of packages."</string>
<string name="permlab_requestDeletePackages" msgid="2541172829260106795">"request delete packages"</string>
<string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Allows an application to request deletion of packages."</string>
- <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"ask to ignore battery optimisations"</string>
- <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimisations for that app."</string>
+ <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"ask to ignore battery optimizations"</string>
+ <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimizations for that app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
@@ -1467,9 +1519,9 @@
<string name="ime_action_done" msgid="6299921014822891569">"Done"</string>
<string name="ime_action_previous" msgid="6548799326860401611">"Prev"</string>
<string name="ime_action_default" msgid="8265027027659800121">"Execute"</string>
- <string name="dial_number_using" msgid="6060769078933953531">"Dial number\n using <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="create_contact_using" msgid="6200708808003692594">"Create contact\n using <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"The following one or more applications request permission to access your account, now and in the future."</string>
+ <string name="dial_number_using" msgid="6060769078933953531">"Dial number\nusing <xliff:g id="NUMBER">%s</xliff:g>"</string>
+ <string name="create_contact_using" msgid="6200708808003692594">"Create contact\nusing <xliff:g id="NUMBER">%s</xliff:g>"</string>
+ <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"The following one or more apps request permission to access your account, now and in the future."</string>
<string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Do you want to allow this request?"</string>
<string name="grant_permissions_header_text" msgid="3420736827804657201">"Access request"</string>
<string name="allow" msgid="6195617008611933762">"Allow"</string>
@@ -1479,7 +1531,7 @@
<string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"Permission requested by <xliff:g id="APP">%1$s</xliff:g>\nfor account <xliff:g id="ACCOUNT">%2$s</xliff:g>."</string>
<string name="forward_intent_to_owner" msgid="4620359037192871015">"You\'re using this app outside of your work profile"</string>
<string name="forward_intent_to_work" msgid="3620262405636021151">"You\'re using this app in your work profile"</string>
- <string name="input_method_binding_label" msgid="1166731601721983656">"Input Method"</string>
+ <string name="input_method_binding_label" msgid="1166731601721983656">"Input method"</string>
<string name="sync_binding_label" msgid="469249309424662147">"Sync"</string>
<string name="accessibility_binding_label" msgid="1974602776545801715">"Accessibility"</string>
<string name="wallpaper_binding_label" msgid="1197440498000786738">"Wallpaper"</string>
@@ -1521,7 +1573,7 @@
<string name="gpsNotifMessage" msgid="7346649122793758032">"Requested by <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SERVICE">%2$s</xliff:g>)"</string>
<string name="gpsVerifYes" msgid="3719843080744112940">"Yes"</string>
<string name="gpsVerifNo" msgid="1671201856091564741">"No"</string>
- <string name="sync_too_many_deletes" msgid="6999440774578705300">"Deletion limit exceeded"</string>
+ <string name="sync_too_many_deletes" msgid="6999440774578705300">"Delete limit exceeded"</string>
<string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"There are <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> deleted items for <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, account <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. What do you want to do?"</string>
<string name="sync_really_delete" msgid="5657871730315579051">"Delete the items"</string>
<string name="sync_undo_deletes" msgid="5786033331266418896">"Undo the deletes"</string>
@@ -1537,8 +1589,8 @@
<string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Decrease minute"</string>
<string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Increase hour"</string>
<string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Decrease hour"</string>
- <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Set p.m."</string>
- <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Set a.m."</string>
+ <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Set PM"</string>
+ <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Set AM"</string>
<string name="date_picker_increment_month_button" msgid="3447263316096060309">"Increase month"</string>
<string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Decrease month"</string>
<string name="date_picker_increment_day_button" msgid="4349336637188534259">"Increase day"</string>
@@ -1590,15 +1642,15 @@
<string name="issued_to" msgid="5975877665505297662">"Issued to:"</string>
<string name="common_name" msgid="1486334593631798443">"Common name:"</string>
<string name="org_name" msgid="7526331696464255245">"Organization:"</string>
- <string name="org_unit" msgid="995934486977223076">"Organisational unit:"</string>
+ <string name="org_unit" msgid="995934486977223076">"Organizational unit:"</string>
<string name="issued_by" msgid="7872459822431585684">"Issued by:"</string>
<string name="validity_period" msgid="1717724283033175968">"Validity:"</string>
<string name="issued_on" msgid="5855489688152497307">"Issued on:"</string>
<string name="expires_on" msgid="1623640879705103121">"Expires on:"</string>
<string name="serial_number" msgid="3479576915806623429">"Serial number:"</string>
<string name="fingerprints" msgid="148690767172613723">"Fingerprints:"</string>
- <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 fingerprint"</string>
- <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 fingerprint"</string>
+ <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 fingerprint:"</string>
+ <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 fingerprint:"</string>
<string name="activity_chooser_view_see_all" msgid="3917045206812726099">"See all"</string>
<string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Choose activity"</string>
<string name="share_action_provider_share_with" msgid="1904096863622941880">"Share with"</string>
@@ -1612,7 +1664,7 @@
<string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string>
<string name="default_audio_route_name" product="default" msgid="9213546147739983977">"Phone"</string>
<string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"Dock speakers"</string>
- <string name="default_audio_route_name_external_device" msgid="8124229858618975">"External device"</string>
+ <string name="default_audio_route_name_external_device" msgid="8124229858618975">"External Device"</string>
<string name="default_audio_route_name_headphones" msgid="6954070994792640762">"Headphones"</string>
<string name="default_audio_route_name_usb" msgid="895668743163316932">"USB"</string>
<string name="default_audio_route_category_name" msgid="5241740395748134483">"System"</string>
@@ -1650,9 +1702,9 @@
<string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
<string name="kg_invalid_puk" msgid="4809502818518963344">"Re-enter the correct PUK code. Repeated attempts will permanently disable the SIM."</string>
- <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN codes do not match"</string>
+ <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN codes does not match"</string>
<string name="kg_login_too_many_attempts" msgid="699292728290654121">"Too many pattern attempts"</string>
- <string name="kg_login_instructions" msgid="3619844310339066827">"To unlock, sign in with your Google Account."</string>
+ <string name="kg_login_instructions" msgid="3619844310339066827">"To unlock, sign in with your Google account."</string>
<string name="kg_login_username_hint" msgid="1765453775467133251">"Username (email)"</string>
<string name="kg_login_password_hint" msgid="3330530727273164402">"Password"</string>
<string name="kg_login_submit_button" msgid="893611277617096870">"Sign in"</string>
@@ -1669,13 +1721,13 @@
<string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER">%d</xliff:g> times. Your Android TV device will now be reset to factory default."</string>
<string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER">%d</xliff:g> times. The phone will now be reset to factory default."</string>
<string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
<string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
<string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
<string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string>
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
- <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
+ <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
@@ -1694,7 +1746,7 @@
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string>
<string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string>
<string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string>
- <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string>
+ <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the accessibility button"</string>
<string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string>
<string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> has been turned off"</string>
<string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit shortcuts"</string>
@@ -1703,15 +1755,15 @@
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Colour inversion"</string>
<string name="color_correction_feature_name" msgid="3655077237805422597">"Colour correction"</string>
- <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
+ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-Handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned off."</string>
<string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Press and hold both volume keys for three seconds to use <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Choose a feature to use when you tap the Accessibility button:"</string>
+ <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Choose a feature to use when you tap the accessibility button:"</string>
<string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with two fingers):"</string>
<string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with three fingers):"</string>
- <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"To switch between features, touch and hold the Accessibility button."</string>
+ <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"To switch between features, touch & hold the accessibility button."</string>
<string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"To switch between features, swipe up with two fingers and hold."</string>
<string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"To switch between features, swipe up with three fingers and hold."</string>
<string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Magnification"</string>
@@ -1763,9 +1815,9 @@
<string name="mediasize_na_junior_legal" msgid="3398084874757748531">"Junior Legal"</string>
<string name="mediasize_na_ledger" msgid="1819497882853940248">"Ledger"</string>
<string name="mediasize_na_tabloid" msgid="6792611672983574375">"Tabloid"</string>
- <string name="mediasize_na_index_3x5" msgid="990821038991491710">"Index Card 3 x 5"</string>
- <string name="mediasize_na_index_4x6" msgid="4414381976602032401">"Index Card 4 x 6"</string>
- <string name="mediasize_na_index_5x8" msgid="4499341583361946948">"Index Card 5 x 8"</string>
+ <string name="mediasize_na_index_3x5" msgid="990821038991491710">"Index Card 3x5"</string>
+ <string name="mediasize_na_index_4x6" msgid="4414381976602032401">"Index Card 4x6"</string>
+ <string name="mediasize_na_index_5x8" msgid="4499341583361946948">"Index Card 5x8"</string>
<string name="mediasize_na_monarch" msgid="4396943937986136896">"Monarch"</string>
<string name="mediasize_na_quarto" msgid="2119101847712239885">"Quarto"</string>
<string name="mediasize_na_foolscap" msgid="5011612828564394648">"Foolscap"</string>
@@ -1828,12 +1880,12 @@
<string name="restr_pin_enter_admin_pin" msgid="1199419462726962697">"Enter admin PIN"</string>
<string name="restr_pin_enter_pin" msgid="373139384161304555">"Enter PIN"</string>
<string name="restr_pin_incorrect" msgid="3861383632940852496">"Incorrect"</string>
- <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Current PIN:"</string>
+ <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Current PIN"</string>
<string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"New PIN"</string>
<string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Confirm new PIN"</string>
<string name="restr_pin_create_pin" msgid="917067613896366033">"Create a PIN for modifying restrictions"</string>
<string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"PINs don\'t match. Try again."</string>
- <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least four digits."</string>
+ <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least 4 digits."</string>
<string name="restr_pin_try_later" msgid="5897719962541636727">"Try again later"</string>
<string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
@@ -1856,8 +1908,8 @@
<string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
- <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
- <string name="battery_saver_description" msgid="8518809702138617167">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
+ <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string>
+ <string name="battery_saver_description" msgid="8518809702138617167">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string>
<string name="data_saver_description" msgid="4995164271550590517">"To help reduce data usage, Data Saver prevents some apps from sending or receiving data in the background. An app you\'re currently using can access data, but may do so less frequently. This may mean, for example, that images don\'t display until you tap them."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Turn on Data Saver?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"Turn on"</string>
@@ -1873,7 +1925,7 @@
<string name="zen_mode_until" msgid="2250286190237669079">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Until you turn off"</string>
- <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Until you turn off Do not disturb"</string>
+ <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Until you turn off Do Not Disturb"</string>
<string name="zen_mode_rule_name_combination" msgid="7174598364351313725">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
<string name="toolbar_collapse_description" msgid="8009920446193610996">"Collapse"</string>
<string name="zen_mode_feature_name" msgid="3785547207263754500">"Do not disturb"</string>
@@ -1905,22 +1957,22 @@
<string name="usb_midi_peripheral_product_name" msgid="2836276258480904434">"USB Peripheral Port"</string>
<string name="floating_toolbar_open_overflow_description" msgid="2260297653578167367">"More options"</string>
<string name="floating_toolbar_close_overflow_description" msgid="3949818077708138098">"Close overflow"</string>
- <string name="maximize_button_text" msgid="4258922519914732645">"Maximise"</string>
+ <string name="maximize_button_text" msgid="4258922519914732645">"Maximize"</string>
<string name="close_button_text" msgid="10603510034455258">"Close"</string>
<string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
<string name="call_notification_answer_action" msgid="5999246836247132937">"Answer"</string>
<string name="call_notification_answer_video_action" msgid="2086030940195382249">"Video"</string>
<string name="call_notification_decline_action" msgid="3700345945214000726">"Decline"</string>
- <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Hang up"</string>
+ <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Hang Up"</string>
<string name="call_notification_incoming_text" msgid="6143109825406638201">"Incoming call"</string>
- <string name="call_notification_ongoing_text" msgid="3880832933933020875">"On-going call"</string>
+ <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Ongoing call"</string>
<string name="call_notification_screening_text" msgid="8396931408268940208">"Screening an incoming call"</string>
- <string name="default_notification_channel_label" msgid="3697928973567217330">"Uncategorised"</string>
+ <string name="default_notification_channel_label" msgid="3697928973567217330">"Uncategorized"</string>
<string name="importance_from_user" msgid="2782756722448800447">"You set the importance of these notifications."</string>
<string name="importance_from_person" msgid="4235804979664465383">"This is important because of the people involved."</string>
<string name="notification_history_title_placeholder" msgid="7748630986182249599">"Custom app notification"</string>
- <string name="user_creation_account_exists" msgid="2239146360099708035">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists)?"</string>
- <string name="user_creation_adding" msgid="7305185499667958364">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
+ <string name="user_creation_account_exists" msgid="2239146360099708035">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+ <string name="user_creation_adding" msgid="7305185499667958364">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
<string name="supervised_user_creation_label" msgid="6884904353827427515">"Add supervised user"</string>
<string name="language_selection_title" msgid="52674936078683285">"Add a language"</string>
<string name="country_selection_title" msgid="5221495687299014379">"Region preference"</string>
@@ -1933,7 +1985,7 @@
<string name="region_picker_section_all" msgid="756441309928774155">"All regions"</string>
<string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
<string name="app_suspended_title" msgid="888873445010322650">"App isn’t available"</string>
- <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> isn’t available at the moment. This is managed by <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
+ <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> isn’t available right now. This is managed by <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"Learn more"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Unpause app"</string>
<string name="work_mode_off_title" msgid="961171256005852058">"Turn on work apps?"</string>
@@ -1959,7 +2011,7 @@
<string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your Android TV device instead."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your tablet instead."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your phone instead."</string>
- <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"This app was built for an older version of Android. It might not work properly and doesn\'t include the latest security and privacy protections. Check for an update or contact the app\'s developer."</string>
+ <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"This app was built for an older version of Android. It might not work properly and doesn\'t include the latest security and privacy protections. Check for an update, or contact the app\'s developer."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
<string name="new_sms_notification_content" msgid="3197949934153460639">"Open SMS app to view"</string>
@@ -1997,21 +2049,21 @@
<string name="time_picker_prompt_label" msgid="303588544656363889">"Type in time"</string>
<string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Switch to text input mode for the time input."</string>
<string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Switch to clock mode for the time input."</string>
- <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Auto-fill options"</string>
- <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Save for AutoFill"</string>
- <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Contents can’t be auto-filled"</string>
- <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"No auto-fill suggestions"</string>
- <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{One auto-fill suggestion}other{# auto-fill suggestions}}"</string>
+ <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Autofill options"</string>
+ <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Save for Autofill"</string>
+ <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Contents can’t be autofilled"</string>
+ <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"No autofill suggestions"</string>
+ <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{One autofill suggestion}other{# autofill suggestions}}"</string>
<string name="autofill_save_title" msgid="7719802414283739775">"Save to "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_type" msgid="3002460014579799605">"Save <xliff:g id="TYPE">%1$s</xliff:g> to "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Save <xliff:g id="TYPE_0">%1$s</xliff:g> and <xliff:g id="TYPE_1">%2$s</xliff:g> to "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
- <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Save <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g> to "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Save <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, and <xliff:g id="TYPE_2">%3$s</xliff:g> to "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
<string name="autofill_update_title" msgid="3630695947047069136">"Update in "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
<string name="autofill_update_title_with_type" msgid="5264152633488495704">"Update <xliff:g id="TYPE">%1$s</xliff:g> in "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
<string name="autofill_update_title_with_2types" msgid="1797514386321086273">"Update <xliff:g id="TYPE_0">%1$s</xliff:g> and <xliff:g id="TYPE_1">%2$s</xliff:g> in "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
- <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
+ <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, and <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
- <string name="autofill_save_no" msgid="9212826374207023544">"No, thanks"</string>
+ <string name="autofill_save_no" msgid="9212826374207023544">"No thanks"</string>
<string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
<string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
@@ -2038,9 +2090,9 @@
<string name="mmcc_imsi_unknown_in_hlr_msim_template" msgid="3688508325248599657">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not provisioned"</string>
<string name="mmcc_illegal_ms_msim_template" msgid="832644375774599327">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not allowed"</string>
<string name="mmcc_illegal_me_msim_template" msgid="4802735138861422802">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not allowed"</string>
- <string name="popup_window_default_title" msgid="6907717596694826919">"Pop-Up Window"</string>
+ <string name="popup_window_default_title" msgid="6907717596694826919">"Popup Window"</string>
<string name="slice_more_content" msgid="3377367737876888459">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
- <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"App version downgraded or isn’t compatible with this shortcut"</string>
+ <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"App version downgraded, or isn’t compatible with this shortcut"</string>
<string name="shortcut_restore_not_supported" msgid="4763198938588468400">"Couldn’t restore shortcut because app doesn’t support backup and restore"</string>
<string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"Couldn’t restore shortcut because of app signature mismatch"</string>
<string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"Couldn’t restore shortcut"</string>
@@ -2048,20 +2100,14 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
<string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Calls and notifications will be muted"</string>
<string name="notification_channel_system_changes" msgid="2462010596920209678">"System changes"</string>
- <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Do not disturb"</string>
+ <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Do Not Disturb"</string>
<string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"New: Do Not Disturb is hiding notifications"</string>
- <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Tap to find out more and change."</string>
+ <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Tap to learn more and change."</string>
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Do Not Disturb has changed"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tap to check what\'s blocked."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Review notification settings"</string>
@@ -2073,17 +2119,17 @@
<string name="notification_appops_camera_active" msgid="8177643089272352083">"Camera"</string>
<string name="notification_appops_microphone_active" msgid="581333393214739332">"Microphone"</string>
<string name="notification_appops_overlay_active" msgid="5571732753262836481">"displaying over other apps on your screen"</string>
- <string name="notification_feedback_indicator" msgid="663476517711323016">"Provide feedback"</string>
- <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"This notification was promoted to default. Tap to provide feedback."</string>
- <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"This notification was demoted to silent. Tap to provide feedback."</string>
+ <string name="notification_feedback_indicator" msgid="663476517711323016">"Provide Feedback"</string>
+ <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"This notification was promoted to Default. Tap to provide feedback."</string>
+ <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"This notification was demoted to Silent. Tap to provide feedback."</string>
<string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"This notification was ranked higher. Tap to provide feedback."</string>
<string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"This notification was ranked lower. Tap to provide feedback."</string>
<string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Enhanced notifications"</string>
- <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Suggested actions and replies are now provided by enhanced notifications. Android adaptive notifications are no longer supported."</string>
+ <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Suggested actions and replies are now provided by enhanced notifications. Android Adaptive Notifications are no longer supported."</string>
<string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
<string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Turn off"</string>
<string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Learn more"</string>
- <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Enhanced notifications replaced Android adaptive notifications in Android 12. This feature shows suggested actions and replies, and organises your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb."</string>
+ <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Enhanced notifications replaced Android Adaptive Notifications in Android 12. This feature shows suggested actions and replies, and organizes your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb."</string>
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string>
<string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string>
<string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string>
@@ -2121,25 +2167,25 @@
<string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Recent Apps"</string>
<string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Notifications"</string>
<string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Quick Settings"</string>
- <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Power Dialogue"</string>
+ <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Power Dialog"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Headset hook"</string>
- <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"On-screen accessibility shortcut"</string>
- <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"On-screen accessibility shortcut chooser"</string>
- <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Accessibility shortcut"</string>
- <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Dismiss notification shade"</string>
- <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"Dpad up"</string>
- <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"Dpad down"</string>
- <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad left"</string>
- <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad right"</string>
- <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad centre"</string>
+ <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Headset Hook"</string>
+ <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"On-screen Accessibility Shortcut"</string>
+ <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"On-screen Accessibility Shortcut Chooser"</string>
+ <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Accessibility Shortcut"</string>
+ <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Dismiss Notification Shade"</string>
+ <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"Dpad Up"</string>
+ <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"Dpad Down"</string>
+ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Left"</string>
+ <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Right"</string>
+ <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Center"</string>
<string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string>
<string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Conversation"</string>
- <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Group conversation"</string>
+ <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Group Conversation"</string>
<string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
<string name="resolver_personal_tab" msgid="2051260504014442073">"Personal"</string>
<string name="resolver_work_tab" msgid="2690019516263167035">"Work"</string>
@@ -2170,7 +2216,7 @@
<string name="PERSOSUBSTATE_SIM_SIM_PUK_ENTRY" msgid="3013902515773728996">"Enter PUK"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK1_ENTRY" msgid="2974411408893410289">"RUIM network1 unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK2_ENTRY" msgid="687618528751880721">"RUIM network2 unlock PIN"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"RUIM HRPD unlock PIN"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"RUIM hrpd unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_CORPORATE_ENTRY" msgid="2715929642540980259">"RUIM corporate unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ENTRY" msgid="8557791623303951590">"RUIM service provider unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_ENTRY" msgid="7382468767274580323">"RUIM unlock PIN"</string>
@@ -2186,8 +2232,8 @@
<string name="PERSOSUBSTATE_SIM_IMPI_ENTRY" msgid="7043865376145617024">"IMPI unlock PIN"</string>
<string name="PERSOSUBSTATE_SIM_NS_SP_ENTRY" msgid="6144227308185112176">"Network subset service provider unlock PIN"</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_IN_PROGRESS" msgid="4233355366318061180">"Requesting SIM network unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Requesting SIM network subset unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Requesting SIM service provider unlock…"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Requesting SIM network subset unlock …"</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Requesting SIM service provider un lock…"</string>
<string name="PERSOSUBSTATE_SIM_CORPORATE_IN_PROGRESS" msgid="4795977251920732254">"Requesting SIM corporate unlock…"</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_PUK_IN_PROGRESS" msgid="1090425878157254446">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_IN_PROGRESS" msgid="6476898876518094438">"Requesting PUK unlock…"</string>
@@ -2197,14 +2243,14 @@
<string name="PERSOSUBSTATE_SIM_SIM_IN_PROGRESS" msgid="6709169861932992750">"Requesting SIM unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK1_IN_PROGRESS" msgid="4013870911606478520">"Requesting RUIM network1 unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK2_IN_PROGRESS" msgid="9032651188219523434">"Requesting RUIM network2 unlock…"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Requesting RUIM HRPD unlock…"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Requesting RUIM hrpd unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="830981927724888114">"Requesting RUIM service provider unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_CORPORATE_IN_PROGRESS" msgid="7851790973098894802">"Requesting RUIM corporate unlock…"</string>
<string name="PERSOSUBSTATE_SIM_SPN_IN_PROGRESS" msgid="1149560739586960121">"Requesting SPN unlock…"</string>
<string name="PERSOSUBSTATE_SIM_SP_EHPLMN_IN_PROGRESS" msgid="5708964693522116025">"Requesting SP Equivalent Home PLMN unlock…"</string>
<string name="PERSOSUBSTATE_SIM_ICCID_IN_PROGRESS" msgid="7288103122966483455">"Requesting ICCID unlock…"</string>
<string name="PERSOSUBSTATE_SIM_IMPI_IN_PROGRESS" msgid="4036752174056147753">"Requesting IMPI unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Requesting network subset service provider unlock…"</string>
+ <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Requesting Network subset service provider unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_IN_PROGRESS" msgid="6737197986936251958">"Requesting RUIM unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_IN_PROGRESS" msgid="5658767775619998623">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_IN_PROGRESS" msgid="665978313257653727">"Requesting PUK unlock…"</string>
@@ -2212,16 +2258,16 @@
<string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_IN_PROGRESS" msgid="2695664012344346788">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_IN_PROGRESS" msgid="2695678959963807782">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_PUK_IN_PROGRESS" msgid="1230605365926493599">"Requesting PUK unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"SIM network unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"SIM network subset unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"SIM service provider unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"SIM corporate unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"SIM Network unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"SIM Network Subset unlock request unsucces sful."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"SIM Service Provider unlock request unsu ccessful."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"SIM Corporate unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_SIM_ERROR" msgid="2472944311643350302">"SIM unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"RUIM network1 unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"RUIM network2 unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"RUIM HRPD unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"RUIM corporate unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"RUIM service provider unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"RUIM Network1 unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"RUIM Network2 unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"RUIM Hrpd unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"RUIM Corporate unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"RUIM Service Provider unlock request un successful."</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_ERROR" msgid="707397021218680753">"RUIM unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_PUK_ERROR" msgid="894358680773257820">"PUK unlock unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ERROR" msgid="352466878146726991">"PUK unlock unsuccessful."</string>
@@ -2239,16 +2285,16 @@
<string name="PERSOSUBSTATE_SIM_ICCID_ERROR" msgid="7559167306794441462">"ICCID unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_IMPI_ERROR" msgid="2782926139511136588">"IMPI unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_NS_SP_ERROR" msgid="1890493954453456758">"Network subset service provider unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"SIM network unlock successful."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"SIM network subset unlock successful."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"SIM service provider unlock successful ."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"SIM corporate unlock successful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"SIM Network unlock successful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"SIM Network Subset unlock successful."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"SIM Service Provider unlock successful ."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"SIM Corporate unlock successful."</string>
<string name="PERSOSUBSTATE_SIM_SIM_SUCCESS" msgid="6975608174152828954">"SIM unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"RUIM network1 unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"RUIM network2 unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"RUIM HRPD unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"RUIM service provider unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"RUIM corporate unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"RUIM Network1 unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"RUIM Network2 unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"RUIM Hrpd unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"RUIM Service Provider unlock successf ul."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"RUIM Corporate unlock successful."</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_SUCCESS" msgid="2526483514124121988">"RUIM unlock successful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_PUK_SUCCESS" msgid="7662200333621664621">"PUK unlock successful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_SUCCESS" msgid="2861223407953766632">"PUK unlock successful."</string>
@@ -2278,14 +2324,14 @@
<string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Unblock device camera"</string>
<string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"For <b><xliff:g id="APP">%s</xliff:g></b> and all apps and services"</string>
<string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Unblock"</string>
- <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensor privacy"</string>
+ <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensor Privacy"</string>
<string name="splash_screen_view_icon_description" msgid="180638751260598187">"Application icon"</string>
<string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Application branding image"</string>
<string name="view_and_control_notification_title" msgid="4300765399209912240">"Check access settings"</string>
<string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> can view and control your screen. Tap to review."</string>
- <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> translated."</string>
+ <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Translated."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Message translated from <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> to <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
- <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Background activity"</string>
+ <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Background Activity"</string>
<string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"An app is draining battery"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"An app is still active"</string>
<string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> is running in the background. Tap to manage battery usage."</string>
@@ -2294,6 +2340,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index c4c19da..fbc4aae 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
@@ -447,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -2048,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
@@ -2294,6 +2340,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index d19199d..564a52d 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
@@ -447,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -2048,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
@@ -2294,6 +2340,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 9037d70..dc34624 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data, and cache sizes"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
@@ -447,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognize physical activity"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -2048,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
@@ -2294,6 +2340,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 9441f27..f74ac2b 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El Identificador de llamadas está predeterminado en no restringido. Llamada siguiente: no restringido"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Servicio no suministrado."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"No puedes cambiar la configuración del identificador de llamadas."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Se cambiaron los datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Puedes cambiar esto cuando quieras en Configuración"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hay ningún servicio de datos móviles"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Servicio de llamadas de emergencia no disponible"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sin servicio de voz"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que la aplicación haga que algunas de sus partes se mantengan persistentes en la memoria. Esto puede limitar la memoria disponible para otras aplicaciones y ralentizar el dispositivo."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ejecutar servicio en primer plano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que la app use servicios en primer plano."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que la aplicación recupere su código, sus datos y los tamaños de la memoria caché."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar la configuración del sistema"</string>
@@ -445,11 +491,15 @@
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acceder a la ubicación en segundo plano"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Esta aplicación puede acceder a la ubicación en cualquier momento, aunque no la estés usando."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar tu configuración de audio"</string>
- <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string>
+ <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y la bocina de salida."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabar audio"</string>
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta app puede grabar audio con el micrófono mientras está en uso."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"grabar audio en segundo plano"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta app puede grabar audio con el micrófono en cualquier momento."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Usar este permiso es peligroso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que la app lea los archivos de video del almacenamiento compartido."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"leer los archivos de imagen del almacenamiento compartido"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que la app lea los archivos de imagen del almacenamiento compartido."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"leer los archivos de imagen y video que el usuario haya seleccionado del almacenamiento compartido"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que la app lea los archivos de imagen y video que selecciones del almacenamiento compartido."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"cambiar o borrar contenido de almacenamiento compartido"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Editar almacen. compartido"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"realizar/recibir llamadas SIP"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR DE TODOS MODOS"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Se detectó una app dañina"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"¿Quieres permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acceso por única vez"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permitir"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo debes permitir que accedan a todos ellos las apps que sean de tu confianza. \n\nSi no permites que esta app acceda a todos los registros del dispositivo, aún puede acceder a sus propios registros. Además, es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo debes permitir que accedan a todos los registros las apps que sean de tu confianza. \n\nTen en cuenta que la app puede acceder a sus propios registros incluso si no permites que acceda a todos los registros del dispositivo. También es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo.\n\nObtén más información en g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No volver a mostrar"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quiere mostrar fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibrarán las llamadas y notificaciones"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del dispositivo desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No se puede acceder a la cámara de la tablet desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"No se puede acceder a este contenido durante una transmisión. Inténtalo en tu teléfono."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Predeterminado del sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARJETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 753aa8b..42fb37d 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"La identificación del emisor presenta el valor predeterminado de no restringido. Siguiente llamada: No restringido"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"El servicio no se suministra."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"No puedes modificar la identificación de emisor."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Datos cambiados a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Puedes cambiar esta opción en cualquier momento en Ajustes"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hay ningún servicio de datos móviles"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Servicio de llamadas de emergencia no disponible"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sin servicio de voz"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que la aplicación haga que algunas de sus partes se mantengan en la memoria. Esto puede limitar la cantidad de memoria disponible para otras aplicaciones y ralentizar el teléfono."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ejecutar servicio en primer plano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que la aplicación use servicios en primer plano."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que la aplicación recupere su código, sus datos y los tamaños de caché."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar los ajustes del sistema"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta aplicación puede grabar audio con el micrófono mientras la estés usando."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Grabar audio en segundo plano"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta aplicación puede grabar audio con el micrófono en cualquier momento."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Este permiso es muy peligroso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que la aplicación lea archivos de vídeo desde tu almacenamiento compartido."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"leer archivos de imagen desde el almacenamiento compartido"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que la aplicación lea archivos de imagen desde tu almacenamiento compartido."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"leer los archivos de imagen y de vídeo que el usuario seleccione del almacenamiento compartido"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que la aplicación lea los archivos de imagen y de vídeo que selecciones del almacenamiento compartido."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"editar/eliminar contenido de almacenamiento compartido"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que app edite contenido de almacenamiento compartido."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"hacer/recibir llamadas SIP"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR IGUALMENTE"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Se ha detectado una aplicación dañina"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"¿Permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir el acceso una vez"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permitir"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, aún podrá acceder a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, podrá seguir accediendo a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo.\n\nObtén más información en g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No volver a mostrar"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quiere mostrar fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Las llamadas y las notificaciones vibrarán"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del teléfono desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No se puede acceder a la cámara del tablet desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"No se puede acceder a este contenido durante una emisión. Prueba en tu teléfono."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Predeterminado del sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARJETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 1789793..dd199d8 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Helistaja ID pole vaikimisi piiratud. Järgmine kõne: pole piiratud"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Teenus pole ette valmistatud."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Helistaja ID seadet ei saa muuta."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiilne andmeside lülitati operaatorile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Saate seda igal ajal seadetes muuta"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobiilne andmesideteenus puudub"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hädaabikõned pole saadaval"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Häälkõned pole saadaval"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Võimaldab rakendusel muuta oma osi mälus püsivaks. See võib piirata teistele (telefoni aeglasemaks muutvatele) rakendustele saadaolevat mälu."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"käita esiplaanil olevat teenust"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"Rakenduse mäluruumi mõõtmine"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Võimaldab rakendusel tuua oma koodi, andmed ja vahemälu suurused"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"muutke süsteemi seadeid"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"See rakendus saab mikrofoniga heli salvestada siis, kui rakendus on kasutusel."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Taustal heli salvestamine."</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"See rakendus saab mikrofoniga heli salvestada mis tahes ajal."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM-kaardile käskluste saatmine"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Lubab rakendusel saata käske SIM-kaardile. See on väga ohtlik."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"füüsiliste tegevuste tuvastamine"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Võimaldab rakendusel lugeda teie jagatud salvestusruumis olevaid videofaile."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lugeda teie jagatud salvestusruumis olevaid pildifaile"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Võimaldab rakendusel lugeda teie jagatud salvestusruumis olevaid pildifaile."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lugeda jagatud salvestusruumis olevaid kasutaja valitud pildi- ja videofaile"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Võimaldab rakendusel lugeda teie jagatud salvestusruumis olevaid valitud pildi- ja videofaile."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"Jagatud salvestusruumi sisu muutmine või kustutamine"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Lubab rakendusel kirjutada jagatud salvestusruumi sisu."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP-kõnede tegemine/vastuvõtmine"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALLI"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"AVA IKKA"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Tuvastati kahjulik rakendus"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Kas anda rakendusele <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> juurdepääs kõigile seadmelogidele?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Luba ühekordne juurdepääs"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ära luba"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda.\n\nLugege lisateavet aadressil g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ära kuva uuesti"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Rakendus <xliff:g id="APP_0">%1$s</xliff:g> soovib näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Muuda"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Kõnede ja märguannete puhul seade vibreerib"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse telefoni kaamerale juurde."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse tahvelarvuti kaamerale juurde"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Sellele ei pääse voogesituse ajal juurde. Proovige juurde pääseda oma telefonis."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Süsteemi vaikeseade"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 8a7d073..548ad23 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Zerbitzua ez da hornitu."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu aldatu deitzailearen identitatearen ezarpena."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> operadorearen datu-konexiora aldatu zara"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Ezarpenak atalean alda dezakezu aukera hori"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ez dago mugikorreko datu-zerbitzurik"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ezin da egin larrialdi-deirik"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ez dago ahots-deien zerbitzurik"</string>
@@ -179,7 +177,7 @@
<string name="low_memory" product="watch" msgid="3479447988234030194">"Erlojuaren memoria beteta dago. Tokia egiteko, ezabatu fitxategi batzuk."</string>
<string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV gailuaren memoria beteta dago. Tokia egiteko, ezabatu fitxategi batzuk."</string>
<string name="low_memory" product="default" msgid="2539532364144025569">"Telefonoaren memoria beteta dago. Tokia egiteko, ezabatu fitxategi batzuk."</string>
- <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Ziurtagiri-emaile bat dago instalatuta}other{Ziurtagiri-emaile bat baino gehiago daude instalatuta}}"</string>
+ <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Autoritate ziurtagiri-emaile bat dago instalatuta}other{Autoritate ziurtagiri-emaile bat baino gehiago daude instalatuta}}"</string>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Hirugarren alderdi ezezagun baten arabera"</string>
<string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Laneko profilen administratzaileak"</string>
<string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"<xliff:g id="MANAGING_DOMAIN">%s</xliff:g> da arduraduna"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Beren zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen die aplikazioei. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta telefonoa motel daiteke."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"abiarazi zerbitzuak aurreko planoan"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Aurreko planoko zerbitzuak erabiltzea baimentzen dio aplikazioari."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"neurtu aplikazioen biltegiratzeko tokia"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Bere kodea, datuak eta cache-tamainak eskuratzeko baimena ematen die aplikazioei."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"aldatu sistemaren ezarpenak"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikazioak abian den bitartean erabil dezake mikrofonoa audioa grabatzeko."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Audioa grabatu atzeko planoan."</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikazioak edonoiz erabil dezake mikrofonoa audioa grabatzeko."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"bidali aginduak SIM txartelera"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM txartelera aginduak bidaltzeko baimena ematen die aplikazioei. Oso arriskutsua da."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"hauteman jarduera fisikoa"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Biltegi partekatuko bideo-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"irakurri biltegi partekatuko irudi-fitxategiak"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Biltegi partekatuko irudi-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"irakurri biltegi partekatuko irudi- eta bideo-fitxategiak"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Biltegi partekatuko irudi- eta bideo-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"aldatu edo ezabatu biltegi partekatuko edukia"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Biltegi partekatuko edukian idazteko baimena ematen die aplikazioei."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"egin/jaso SIP deiak"</string>
@@ -1458,7 +1510,7 @@
<string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Paketeak ezabatzeko eskatzea baimentzen die aplikazioei."</string>
<string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"eskatu bateria-optimizazioei ez ikusi egitea"</string>
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Bateriaren optimizazioei ez ikusi egiteko baimena eskatzea baimentzen die aplikazioei."</string>
- <string name="permlab_queryAllPackages" msgid="2928450604653281650">"Kontsultatu pakete guztiak"</string>
+ <string name="permlab_queryAllPackages" msgid="2928450604653281650">"kontsultatu pakete guztiak"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Instalatutako pakete guztiak ikusteko baimena ematen dio aplikazioari."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Sakatu birritan zooma kontrolatzeko"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Ezin izan da widgeta gehitu."</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALATU"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IREKI, HALA ERE"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplikazio kaltegarri bat hauteman da"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gailuko erregistro guztiak atzitzeko baimena eman nahi diozu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aplikazioari?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eman behin erabiltzeko baimena"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ez eman baimenik"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea.\n\nLortu informazio gehiago g.co/android/devicelogs helbidean."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ez erakutsi berriro"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> aplikazioak <xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakutsi nahi ditu"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editatu"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Dar-dar egingo du deiak eta jakinarazpenak jasotzean"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ezin da atzitu telefonoaren kamera <xliff:g id="DEVICE">%1$s</xliff:g> gailutik"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ezin da atzitu tabletaren kamera <xliff:g id="DEVICE">%1$s</xliff:g> gailutik"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ezin da atzitu edukia hura igorri bitartean. Oraingo gailuaren ordez, erabili telefonoa."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Sistemaren balio lehenetsia"</string>
<string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> TXARTELA"</string>
</resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 88bbcc6..07fa710 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"پیشفرض شناسه تماسگیرنده روی غیرمحدود است. تماس بعدی: بدون محدودیت"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"سرویس دارای مجوز نیست."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"شما میتوانید تنظیم شناسه تماسگیرنده را تغییر دهید."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"داده به <xliff:g id="CARRIERDISPLAY">%s</xliff:g> تغییر کرد"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"هرزمان بخواهید میتوانید این را در «تنظیمات» تغییر دهید"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"بدون سرویس داده تلفن همراه"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"تماس اضطراری دردسترس نیست"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"سرویس صوتی دردسترس نیست"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"به برنامه امکان میدهد قسمتهایی از خود را در حافظه دائمی کند. این کار حافظه موجود را برای سایر برنامهها محدود کرده و باعث کندی تلفن میشود."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"اجرای سرویس پیشزمینه"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"به برنامه اجازه میدهد از سرویسهای پیشزمینه استفاده کند."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"اندازهگیری اندازه فضای ذخیرهسازی برنامه"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"به برنامه اجازه میدهد تا کدها، دادهها و اندازههای حافظهٔ پنهان خود را بازیابی کند"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"تغییر تنظیمات سیستم"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"این برنامه وقتی درحال استفاده است، میتواند بااستفاده از میکروفون صدا ضبط کند."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ضبط صدا در پسزمینه"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"این برنامه میتواند در هرزمانی با استفاده از میکروفون صدا ضبط کند."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"ارسال فرمان به سیم کارت"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"به برنامه اجازه ارسال دستورات به سیم کارت را میدهد. این بسیار خطرناک است."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"تشخیص فعالیت فیزیکی"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"به برنامه اجازه میدهد فایلهای ویدیویی موجود در فضای ذخیرهسازی همرسانیشده را بخواند."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"خواندن فایلهای تصویری موجود در فضای ذخیرهسازی مشترک"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"به برنامه اجازه میدهد فایلهای تصویری موجود در فضای ذخیرهسازی مشترک را بخواند."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"خواندن فایلهای تصویری و ویدیویی انتخابی کاربر از فضای ذخیرهسازی مشترک"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"به برنامه جازه میدهد فایلهای تصویری و ویدیوییای را که از فضای ذخیرهسازی مشترکتان انتخاب میکنید بخواند."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"تغییر یا حذف محتوای فضای ذخیرهسازی مشترک"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"به برنامه اجازه میدهد محتوای فضای ذخیرهسازی مشترکتان را بنویسد."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"تماس گرفتن/دریافت تماس از طریق SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"حذف نصب"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"درهرصورت باز شود"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"برنامه مضر شناسایی شد"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"به <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> اجازه میدهید به همه گزارشهای دستگاه دسترسی داشته باشد؟"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"مجاز کردن دسترسی یکباره"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازه ندادن"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"گزارشهای دستگاه آنچه را در دستگاهتان رخ میدهد ثبت میکند. برنامهها میتوانند از این گزارشها برای پیدا کردن مشکلات و رفع آنها استفاده کنند.\n\nبرخیاز گزارشها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامههای مورداعتمادتان اجازه دسترسی به همه گزارشهای دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارشهای دستگاه دسترسی داشته باشد، همچنان میتواند به گزارشهای خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخیاز گزارشها یا اطلاعات دستگاهتان دسترسی داشته باشد."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"گزارشهای دستگاه آنچه را در دستگاهتان رخ میدهد ثبت میکند. برنامهها میتوانند از این گزارشها برای پیدا کردن مشکلات و رفع آنها استفاده کنند.\n\nبرخیاز گزارشها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامههای مورداعتمادتان اجازه دسترسی به همه گزارشهای دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارشهای دستگاه دسترسی داشته باشد، همچنان میتواند به گزارشهای خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخیاز گزارشها یا اطلاعات دستگاهتان دسترسی داشته باشد.\n\nاطلاعات بیشتر: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوباره نشان داده نشود"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> میخواهد تکههای <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ویرایش"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"دستگاهتان برای تماسها و اعلانها میلرزد"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"از <xliff:g id="DEVICE">%1$s</xliff:g> به دوربین تلفن دسترسی ندارید"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"نمیتوان از <xliff:g id="DEVICE">%1$s</xliff:g> شما به دوربین رایانه لوحی دسترسی داشت"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"درحین جاریسازی، نمیتوانید به آن دسترسی داشته باشید. دسترسی به آن را در تلفنتان امتحان کنید."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"پیشفرض سیستم"</string>
<string name="default_card_name" msgid="9198284935962911468">"کارت <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index da3a91e..ce6e579 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Soittajan tunnukseksi muutetaan rajoittamaton. Seuraava puhelu: ei rajoitettu"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Palvelua ei tarjota."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Et voi muuttaa soittajan tunnuksen asetusta."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Data vaihdettu: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Voit vaihtaa valintasi milloin tahansa asetuksista."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ei mobiilidatapalvelua"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hätäpuhelut eivät ole käytettävissä"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ei äänipuheluja"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Antaa sovelluksen lisätä omia osiaan muistiin pysyvästi. Tämä voi rajoittaa muiden sovellusten käytettävissä olevaa muistia ja hidastaa puhelimen toimintaa."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"suorita etualan palvelu"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Sallii sovelluksen käyttää etualan palveluja"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"sovellusten tallennustilan mittaaminen"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Antaa sovelluksen noutaa sen koodin, tietojen ja välimuistin koot."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"muokkaa järjestelmän asetuksia"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Tämä sovellus voi tallentaa mikrofonilla audiota, kun sovellusta käytetään."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"tallentaa audiota taustalla"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Tämä sovellus voi tallentaa mikrofonilla audiota koska tahansa."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"lähettää komentoja SIM-kortille"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Antaa sovelluksen lähettää komentoja SIM-kortille. Tämä ei ole turvallista."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"tunnistaa liikkumisen"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Sallii sovelluksen lukea jaetun tallennustilan videotiedostoja."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lue jaetun tallennustilan kuvatiedostoja"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Sallii sovelluksen lukea jaetun tallennustilan kuvatiedostoja."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lukulupa jaetun tallennustilan kuva- ja videotiedostoihin, jotka käyttäjä on valinnut"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Sallii sovelluksen lukea kuva- ja videotiedostoja, joita valitset jaetusta tallennustilasta."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"muokata tai poistaa jaetun tallennustilan sisältöä"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Antaa sovelluksen kirjoittaa jaetun tallennustilan sisällön."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"soita/vastaanota SIP-puheluja"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"POISTA"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"AVAA SILTI"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Haitallinen sovellus havaittu"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Saako <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> pääsyn kaikkiin laitelokeihin?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Salli kertaluonteinen pääsy"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Älä salli"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella.\n\nLue lisää osoitteessa g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Älä näytä uudelleen"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> haluaa näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>."</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Muokkaa"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Puhelut ja ilmoitukset värisevät"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> ei pääse puhelimen kameraan"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> ei pääse tabletin kameraan"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Sisältöön ei saa pääsyä striimauksen aikana. Kokeile striimausta puhelimella."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Järjestelmän oletusarvo"</string>
<string name="default_card_name" msgid="9198284935962911468">"Kortti: <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index d05d97b..51f6db8 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Données changées à <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Vous pouvez modifier cette option en tout temps dans les paramètres"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Aucun service de données cellulaires"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Le service d\'appel d\'urgence n\'est pas accessible"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Aucun service vocal"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permet à l\'application de rendre certains de ces composants persistants dans la mémoire. Cette autorisation peut limiter la mémoire disponible pour d\'autres applications et ralentir le téléphone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"exécuter le service en premier plan"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permet à l\'application d\'utiliser les services en premier plan."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permet à l\'application de récupérer la taille de son code, de ses données et de sa mémoire cache."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modifier les paramètres du système"</string>
@@ -450,6 +496,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Cette application peut enregistrer de l\'audio à l\'aide du microphone lorsque vous utilisez l\'application."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"enregistrer de l\'audio en arrière-plan"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Cette application peut enregistrer de l\'audio à l\'aide du microphone en tout temps."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"détecter les captures d\'écran des fenêtres d\'application"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Cette application recevra une notification lorsqu\'une capture d\'écran sera prise pendant que l\'application est en cours d\'utilisation."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet à l\'application d\'envoyer des commandes à la carte SIM. Cette fonctionnalité est très dangereuse."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître les activités physiques"</string>
@@ -701,6 +749,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permet à l\'application de lire les fichiers vidéo de votre espace de stockage partagé."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lire des fichiers d\'image à partir de l\'espace de stockage partagé"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permet à l\'application de lire les fichiers d\'image de votre espace de stockage partagé."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lire les fichiers d\'images et de vidéos sélectionnés par l\'utilisateur dans l\'espace de stockage partagé"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permet à l\'application de lire les fichiers d\'images et de vidéos que vous sélectionnez dans votre espace de stockage partagé."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modifier ou supprimer le contenu de votre espace de stockage partagé"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Autorise l\'application à écrire le contenu de votre espace de stockage partagé."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"faire et recevoir des appels SIP"</string>
@@ -2051,12 +2101,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DÉSINSTALLER"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OUVRIR QUAND MÊME"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Une application nuisible a été détectée"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à l\'ensemble des journaux de l\'appareil?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Autoriser un accès unique"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne pas autoriser"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil.\n\nPour en savoir plus, consultez la page g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne plus afficher"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> souhaite afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Modifier"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Les appels et les notifications vibreront"</string>
@@ -2297,6 +2341,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone à partir de votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossible d\'accéder à l\'appareil photo de la tablette à partir de votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Vous ne pouvez pas y accéder lorsque vous utilisez la diffusion en continu. Essayez sur votre téléphone à la place."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Paramètre système par défaut"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index cb2e201..8d84a39 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Données transférées vers <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Vous pouvez modifier cette option à tout moment dans les paramètres."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Aucun service de données mobiles"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Appels d\'urgence non disponibles"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Aucun service vocal"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permet à l\'application de rendre certains de ces composants persistants dans la mémoire. Cette autorisation peut limiter la mémoire disponible pour d\'autres applications et ralentir le téléphone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"exécuter un service de premier plan"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Autorise l\'application à utiliser des services de premier plan."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permet à l\'application de récupérer son code, ses données et la taille de sa mémoire cache."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modifier les paramètres du système"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Cette application peut utiliser le micro pour réaliser des enregistrements audio quand elle est en cours d\'utilisation."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"réaliser des enregistrements audio en arrière-plan"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Cette application peut utiliser le micro pour réaliser des enregistrements audio à tout moment."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Autoriser l\'envoi de commandes à la carte SIM via l\'application. Cette fonctionnalité est très risquée."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître l\'activité physique"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permettre à l\'application de lire les fichiers vidéo de votre espace de stockage partagé."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lire les fichiers image de l\'espace de stockage partagé"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permettre à l\'application de lire les fichiers image de votre espace de stockage partagé."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lire les fichiers image et vidéo sélectionnés par l\'utilisateur dans le stockage partagé"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permet à l\'appli de lire les fichiers image et vidéo que vous sélectionnez dans votre stockage partagé."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modifier/supprimer contenu mémoire stockage partagée"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permet de modifier le contenu mémoire de stockage partagée."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"effectuer/recevoir des appels SIP"</string>
@@ -2006,7 +2058,7 @@
<string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Aucune suggestion de saisie automatique"</string>
<string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{1 suggestion de saisie automatique}one{# suggestion de saisie automatique}many{# suggestions de saisie automatique}other{# suggestions de saisie automatique}}"</string>
<string name="autofill_save_title" msgid="7719802414283739775">"Enregistrer dans "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>" ?"</string>
- <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Enregistrer la <xliff:g id="TYPE">%1$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ?"</string>
+ <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Enregistrer le <xliff:g id="TYPE">%1$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ?"</string>
<string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Enregistrer <xliff:g id="TYPE_0">%1$s</xliff:g> et <xliff:g id="TYPE_1">%2$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>" ?"</string>
<string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Enregistrer <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> et <xliff:g id="TYPE_2">%3$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ?"</string>
<string name="autofill_update_title" msgid="3630695947047069136">"Mettre à jour cet élément dans "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>" ?"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DÉSINSTALLER"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OUVRIR QUAND MÊME"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Application dangereuse détectée"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à tous les journaux de l\'appareil ?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Autoriser un accès unique"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne pas autoriser"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre les problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre des problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil.\n\nEn savoir plus sur g.co/android/devicelogs"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne plus afficher"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> souhaite afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Modifier"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Les appels et les notifications vibreront"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone depuis votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossible d\'accéder à l\'appareil photo de la tablette depuis votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Impossible d\'accéder à cela pendant le streaming. Essayez plutôt sur votre téléphone."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Paramètre système par défaut"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index a3aed41..57fba7f 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizo non ofrecido."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Non podes cambiar a configuración do identificador de chamada."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Cambiouse a conexión de datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Podes cambiar esta opción en calquera momento en Configuración"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Non hai servizo de datos para móbiles"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"As chamadas de emerxencia non están dispoñibles"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Non hai servizo de chamadas de voz"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite á aplicación converter partes súas como persistentes na memoria. Esta acción pode limitar a cantidade memoria dispoñible para outras aplicacións e reducir a velocidade de funcionamento do teléfono."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"executar un servizo en primeiro plano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que a aplicación utilice os servizos en primeiro plano."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espazo de almacenamento da aplicación"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite á aplicación recuperar o código, os datos e os tamaños da caché"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar a configuración do sistema"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Mentres a empregas, esta aplicación pode utilizar o micrófono para gravar audio."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar audio en segundo plano"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta aplicación pode utilizar o micrófono en calquera momento para gravar audio."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos á SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite á aplicación enviar comandos á SIM. Isto é moi perigoso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recoñecer actividade física"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que a aplicación acceda a ficheiros de vídeo do almacenamento compartido."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"acceder a ficheiros de imaxe do almacenamento compartido"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que a aplicación acceda a ficheiros de imaxe do almacenamento compartido."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler ficheiros de imaxe e de vídeo do almacenamento compartido seleccionados polo usuario"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que a aplicación lea os ficheiros de imaxe e de vídeo que selecciones do almacenamento compartido."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modificar ou eliminar o almacenamento compartido"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite á aplicación escribir no almacenamento compartido."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"facer/recibir chamadas SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR IGUALMENTE"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Detectouse unha aplicación daniña"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Queres permitir que a aplicación <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos os rexistros do dispositivo?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acceso unha soa vez"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Non permitir"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo.\n\nConsulta máis información en g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Non amosar outra vez"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quere mostrar fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"As chamadas e as notificacións vibrarán"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Non se puido acceder á cámara do teléfono desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Non se puido acceder á cámara da tableta desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Non se puido acceder a este contido durante a reprodución en tempo real. Téntao desde o teléfono."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Opción predeterminada do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARXETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 2d9210b..c89b02b 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"સેવાની જોગવાઈ કરી નથી."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"તમે કૉલર ID સેટિંગ બદલી શકતાં નથી."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"ડેટા <xliff:g id="CARRIERDISPLAY">%s</xliff:g> પર સ્વિચ કર્યો"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"સેટિંગમાં તમે આને કોઈપણ સમયે બદલી શકો છો"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"કોઈ મોબાઇલ ડેટા સેવા નથી"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"કોટોકટીની કૉલિંગ સેવા અનુપલબ્ધ"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"કોઈ વૉઇસ સેવા નથી"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"એપ્લિકેશનને મેમરીમાં પોતાના ભાગ સતત બનાવવાની મંજૂરી આપે છે. આ ફોનને ધીમો કરીને અન્ય ઍપ્લિકેશનો પર ઉપલબ્ધ મેમરીને સીમિત કરી શકે છે."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ઍપને ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ઍપ્લિકેશન સંગ્રહ સ્થાન માપો"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"એપ્લિકેશનને તેનો કોડ, ડેટા અને કેશ કદ પુનઃપ્રાપ્ત કરવાની મંજૂરી આપે છે."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"સિસ્ટમ સેટિંગમાં ફેરફાર કરો"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"આ ઍપ ઉપયોગમાં હોય ત્યારે તે માઇક્રોફોનનો ઉપયોગ કરીને ઑડિયો રેકોર્ડ કરી શકે છે."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"બૅકગ્રાઉન્ડમાં ઑડિયો રેકોર્ડ કરો"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"આ ઍપ, માઇક્રોફોનનો ઉપયોગ કરીને કોઈપણ સમયે ઑડિયો રેકોર્ડ કરી શકે છે."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"સિમ ને આદેશો મોકલો"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"એપ્લિકેશનને સિમ પરા આદેશો મોકલવાની મંજૂરી આપે છે. આ ખૂબ જ ખતરનાક છે."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"શારીરિક પ્રવૃત્તિને ઓળખો"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ઍપને તમારા શેર કરાયેલા સ્ટોરેજમાંથી વીડિયો ફાઇલો વાંચવાની મંજૂરી આપે છે."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"શેર કરાયેલા સ્ટોરેજમાંથી છબી ફાઇલો વાંચવા માટે"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ઍપને તમારા શેર કરાયેલા સ્ટોરેજમાંથી છબી ફાઇલો વાંચવાની મંજૂરી આપે છે."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"શેર કરેલા સ્ટોરેજમાંથી વપરાશકર્તા દ્વારા પસંદ કરેલી છબી અને વીડિયો ફાઇલો વાંચો"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ઍપને તમારા શેર કરેલા સ્ટોરેજમાંથી તમે પસંદ કરેલી છબી અને વીડિયો ફાઇલો વાંચવાની મંજૂરી આપે છે."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"શેર કરેલા સ્ટોરેજ કન્ટેન્ટમાં ફેરફાર કરો/ડિલીટ કરો"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"શેર કરેલા સ્ટોરેજ કન્ટેન્ટમાં લખવાની મંજૂરી આપે છે."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP કૉલ્સ કરો/પ્રાપ્ત કરો"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"અનઇન્સ્ટૉલ કરો"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"કોઈપણ રીતે ખોલો"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"નુકસાનકારક ઍપ મળી આવી છે"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી આપવી છે?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"એક-વખતના ઍક્સેસની મંજૂરી આપો"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"મંજૂરી આપશો નહીં"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજુ પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજુ પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે.\n\ng.co/android/devicelogs પર વધુ જાણો."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ફરીથી બતાવશો નહીં"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>એ <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવા માગે છે"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ફેરફાર કરો"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"કૉલ અને નોટિફિકેશન માટે ઉપકરણ વાઇબ્રેટ થશે"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પરથી ફોનના કૅમેરાનો ઍક્સેસ કરી શકતાં નથી"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પરથી ટૅબ્લેટના કૅમેરાનો ઍક્સેસ કરી શકતાં નથી"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"સ્ટ્રીમ કરતી વખતે આ ઍક્સેસ કરી શકાતું નથી. તેના બદલે તમારા ફોન પર પ્રયાસ કરો."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"સિસ્ટમ ડિફૉલ્ટ"</string>
<string name="default_card_name" msgid="9198284935962911468">"કાર્ડ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 9f0e936..c544df4 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित नहीं है. अगली कॉल: सीमित नहीं"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवा प्रावधान की हुई नहीं है."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"आप कॉलर आईडी सेटिंग नहीं बदल सकते."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"डेटा को <xliff:g id="CARRIERDISPLAY">%s</xliff:g> पर स्विच किया गया"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"सेटिंग में जाकर, इसे कभी भी बंद किया जा सकता है"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"मोबाइल डेटा सेवा पर रोक लगा दी गई है"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आपातकालीन कॉल पर रोक लगा दी गई है"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"कोई वॉइस सेवा नहीं है"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ऐप्स को मेमोरी में स्वयं के कुछ हिस्सों को लगातार बनाने देता है. यह अन्य ऐप्स के लिए उपलब्ध स्मृति को सीमित कर फ़ोन को धीमा कर सकता है."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"स्क्रीन पर दिखने वाली सेवा चालू करें"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ऐप्लिकेशन को स्क्रीन पर दिखने वाली सेवाएं इस्तेमाल करने दें."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"पता करें कि ऐप मेमोरी में कितनी जगह है"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ऐप को उसका कोड, डेटा, और कैश मेमोरी के आकारों को फिर से पाने देता है"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"सिस्टम सेटिंग बदलें"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"जब इस ऐप्लिकेशन का इस्तेमाल किया जा रहा हो, तब यह माइक्रोफ़ोन का इस्तेमाल करके ऑडियो रिकॉर्ड कर सकता है."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ऐप्लिकेशन बैकग्राउंड में ऑडियो रिकॉर्ड कर सकता है"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"यह ऐप्लिकेशन जब चाहे, माइक्रोफ़ोन का इस्तेमाल करके ऑडियो रिकॉर्ड कर सकता है."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"सिम पर निर्देश भेजें"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ऐप को सिम पर निर्देश भेजने देती है. यह बहुत ही खतरनाक है."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"शरीर की गतिविधि को पहचानना"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"अपने डिवाइस के शेयर किए गए स्टोरेज से, ऐप्लिकेशन को वीडियो फ़ाइलें पढ़ने की अनुमति दें."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"डिवाइस के शेयर किए गए स्टोरेज से, इमेज फ़ाइलें देखने की अनुमति"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"अपने डिवाइस के शेयर किए गए स्टोरेज से, ऐप्लिकेशन को इमेज फ़ाइलें देखने की अनुमति दें."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"डिवाइस के शेयर किए गए स्टोरेज से चुनी गई इमेज और वीडियो फ़ाइलों को देखने की अनुमति दें"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"इससे ऐप्लिकेशन को, डिवाइस के शेयर किए गए स्टोरेज से चुनी गई इमेज और वीडियो फ़ाइलों को देखने की अनुमति मिलती है."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"आपकी शेयर की गई मेमोरी की सामग्री में बदलाव करना या उसे मिटाना"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ऐप्लिकेशन को आपकी शेयर की गई मेमोरी की सामग्री लिखने देती है."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP कॉल करें/पाएं"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"अनइंस्टॉल करें"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"फिर भी खोलें"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"नुकसान पहुंचाने वाले ऐप का पता चला"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"क्या <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> को डिवाइस लॉग का ऐक्सेस देना है?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक बार ऐक्सेस करने की अनुमति दें"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति न दें"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिवाइस लॉग में, आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें ठीक करने के लिए करते हैं.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस लॉग का ऐक्सेस दें. \n\nअगर इस ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी यह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"डिवाइस लॉग में आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें सही करने के लिए करता है.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस दें. \n\nअगर किसी ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी वह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है.\n\nज़्यादा जानने के लिए, g.co/android/devicelogs पर जाएं."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फिर से न दिखाएं"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>, <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाना चाहता है"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"बदलाव करें"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कॉल और सूचनाओं आने पर डिवाइस वाइब्रेट हाेगा"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"आपके <xliff:g id="DEVICE">%1$s</xliff:g> से फ़ोन के कैमरे को ऐक्सेस नहीं किया जा सकता"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"आपके <xliff:g id="DEVICE">%1$s</xliff:g> से टैबलेट के कैमरे को ऐक्सेस नहीं किया जा सकता"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रीमिंग के दौरान, इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने फ़ोन पर ऐक्सेस करके देखें."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"सिस्टम डिफ़ॉल्ट"</string>
<string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 40a49db..e4679fe 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -396,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Aplikaciji omogućuje trajnu prisutnost nekih njenih dijelova u memoriji. To može ograničiti dostupnost memorije drugim aplikacijama i usporiti telefon."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"pokrenuti uslugu u prednjem planu"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Omogućuje aplikaciji da upotrebljava usluge koje su u prednjem planu."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora za pohranu aplikacije"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Aplikaciji omogućuje dohvaćanje koda, podataka i veličine predmemorije"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"izmjena postavki sustava"</string>
@@ -448,6 +496,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikacija može snimati audiozapise pomoću mikrofona dok se upotrebljava."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snimati audiozapise u pozadini"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikacija može snimati audiozapise pomoću mikrofona u svakom trenutku."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detektirati snimanja zaslona prozora aplikacije"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ako se tijekom upotrebe ove aplikacije izradi snimka zaslona, aplikacija će dobiti obavijest."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"slati naredbe SIM-u"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućuje aplikaciji slanje naredbi SIM-u. To je vrlo opasno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznati tjelesnu aktivnost"</string>
@@ -699,6 +749,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Aplikaciji omogućuje čitanje videodatoteka iz dijeljene pohrane."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"čitati slikovne datoteke iz dijeljene pohrane"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Aplikaciji omogućuje čitanje slikovnih datoteka iz dijeljene pohrane."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čitati slikovne i videodatoteke koje je korisnik odabrao iz dijeljene pohrane"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Aplikaciji omogućuje čitanje slikovnih i videodatoteka koje odaberete iz dijeljene pohrane."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"izmjena ili brisanje sadržaja dijeljene pohrane"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Aplikaciji omogućuje pisanje sadržaja u dijeljenu pohranu."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"upućivanje/primanje SIP poziva"</string>
@@ -2049,12 +2101,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALIRAJ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IPAK OTVORI"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Otkrivena je štetna aplikacija"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Želite li dopustiti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Omogući jednokratni pristup"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nemoj dopustiti"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> želi prikazivati isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Uređaj će vibrirati za pozive i obavijesti"</string>
@@ -2295,6 +2341,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"S vašeg uređaja <xliff:g id="DEVICE">%1$s</xliff:g> nije moguće pristupiti fotoaparatu telefona"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"S vašeg uređaja <xliff:g id="DEVICE">%1$s</xliff:g> nije moguće pristupiti fotoaparatu tableta"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Sadržaju nije moguće pristupiti tijekom streaminga. Pokušajte mu pristupiti na telefonu."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Zadane postavke sustava"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index cf6132f..68bed06 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"A hívóazonosító alapértelmezett értéke nem korlátozott. Következő hívás: nem korlátozott"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"A szolgáltatás nincs biztosítva."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nem tudja módosítani a hívó fél azonosítója beállítást."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Adatforgalom átváltva a következőre: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"A Beállításokban ezt bármikor módosíthatja"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nincs mobiladat-szolgáltatás"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Segélyhívás nem lehetséges"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hangszolgáltatás letiltva"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Lehetővé teszi az alkalmazás számára, hogy egyes részeit állandó jelleggel eltárolja a memóriában. Ez korlátozhatja a többi alkalmazás számára rendelkezésre álló memóriát, és lelassíthatja a telefont."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"előtérben lévő szolgáltatás futtatása"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"alkalmazás-tárhely felmérése"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Lehetővé teszi az alkalmazás számára a kód, az adatok és a gyorsítótár-méret lekérését"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"rendszerbeállítások módosítása"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Az alkalmazás a mikrofon használatával akkor készíthet hangfelvételt, amikor az alkalmazás használatban van."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"hangfelvétel készítése a háttérben"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Az alkalmazás a mikrofon használatával bármikor készíthet hangfelvételt."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"parancsok küldése a SIM-kártyára"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Engedélyezi, hogy az alkalmazás parancsokat küldjön a SIM kártyára. Ez rendkívül veszélyes lehet."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"testmozgás felismerése"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Engedélyezi az alkalmazásnak a megosztott tárhelyen található videófájlok olvasását."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"a megosztott tárhelyen található képfájlok olvasása"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Engedélyezi az alkalmazásnak a megosztott tárhelyen található képfájlok olvasását."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"a felhasználó által kiválasztott, megosztott tárhelyen található kép- és videófájlok olvasása"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Engedélyezi az alkalmazásnak a megosztott tárhelyen található, Ön által kiválasztott kép- és videófájlok olvasását."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"a közös tárhely tartalmainak törlése és módosítása"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Engedélyezi az alkalmazás számára a közös tárhely tartalmainak felülírását."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP-hívások indítása/fogadása"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ELTÁVOLÍTÁS"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"MEGNYITÁS MÉGIS"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"A rendszer kártékony alkalmazást észlelt"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Engedélyezi a(z) <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> számára, hogy hozzáférjen az összes eszköznaplóhoz?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Egyszeri hozzáférés engedélyezése"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tiltás"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz.\n\nTovábbi információ: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne jelenjen meg újra"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"A(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazás részleteket szeretne megjeleníteni a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Szerkesztés"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"A hívások és az értesítések rezegnek"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nem lehet hozzáférni a telefon kamerájához a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nem lehet hozzáférni a táblagép kamerájához a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ehhez a tartalomhoz nem lehet hozzáférni streamelés közben. Próbálja újra a telefonján."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Rendszerbeállítás"</string>
<string name="default_card_name" msgid="9198284935962911468">"KÁRTYA: <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 4da9e3b..4f053f3 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Ծառայությունը չի տրամադրվում:"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Դուք չեք կարող փոխել զանգողի ID-ի կարգավորումները:"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Օգտագործվում է <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-ի բջջային ինտերնետը"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Այս գործառույթը կարող եք փոփոխել Կարգավորումներում"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Բջջային ինտերնետի ծառայությունն արգելափակված է"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Շտապ կանչերը հասանելի չեն"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ձայնային ծառայությունն անհասանելի է"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Թույլ է տալիս հավելվածին մնայուն դարձնել իր մասերը հիշողության մեջ: Սա կարող է սահմանափակել այլ հավելվածներին հասանելի հիշողությունը` դանդաղեցնելով հեռախոսի աշխատանքը:"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"աշխատեցնել ակտիվ ծառայությունները"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Թույլ է տալիս հավելվածին օգտագործել ակտիվ ծառայությունները:"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"չափել հավելվածի պահոցի տարածքը"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Թույլ է տալիս հավելվածին առբերել իր կոդը, տվյալները և քեշի չափերը"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"փոփոխել համակարգի կարգավորումները"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Այս հավելվածը կարող է օգտագործել խոսափողը՝ ձայնագրություններ անելու համար, միայն երբ հավելվածն ակտիվ է։"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ձայնագրել ֆոնային ռեժիմում"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Այս հավելվածը կարող է ցանկացած ժամանակ օգտագործել խոսափողը՝ ձայնագրություններ անելու համար։"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"ուղարկել հրամաններ SIM քարտին"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Թույլ է տալիս հավելվածին հրամաններ ուղարկել SIM-ին: Սա շատ վտանգավոր է:"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ֆիզիկական ակտիվության ճանաչում"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Հավելվածին թույլ է տալիս կարդալ ձեր սարքի ընդհանուր հիշողության մեջ պահված վիդեո ֆայլերը։"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"կարդալ ձեր սարքի ընդհանուր հիշողության մեջ պահված գրաֆիկական ֆայլերը"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Հավելվածին թույլ է տալիս կարդալ ձեր սարքի ընդհանուր հիշողության մեջ պահված գրաֆիկական ֆայլերը։"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"կարդալ օգտատիրոջ ընտրած գրաֆիկական և վիդեո ֆայլերն ընդհանուր պահոցից"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Հավելվածին թույլ է տալիս կարդալ գրաֆիկական և վիդեո ֆայլերը, որոնք ընտրել եք ընդհանուր պահոցից։"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"փոփոխել կամ ջնջել ձեր ընդհանուր հիշողության բովանդակությունը"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Հավելվածին թույլ է տալիս փոփոխել ձեր ընդհանուր հիշողության պարունակությունը:"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"կատարել կամ ստանալ SIP զանգեր"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ԱՊԱՏԵՂԱԴՐԵԼ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ԲԱՑԵԼ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Հայտնաբերվել է վնասաբեր հավելված"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Հասանելի դարձնե՞լ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> հավելվածին սարքի բոլոր մատյանները"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Թույլատրել մեկանգամյա մուտքը"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Չթույլատրել"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։\n\nՄանրամասների համար այցելեք g.co/android/devicelogs էջ։"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Այլևս ցույց չտալ"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> հավելվածն ուզում է ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Փոփոխել"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Զանգերի և ծանուցումների համար թրթռոցը միացված է"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Հնարավոր չէ օգտագործել հեռախոսի տեսախցիկը ձեր <xliff:g id="DEVICE">%1$s</xliff:g> սարքից"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Հնարավոր չէ օգտագործել պլանշետի տեսախցիկը ձեր <xliff:g id="DEVICE">%1$s</xliff:g> սարքից"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Այս բովանդակությունը հասանելի չէ հեռարձակման ընթացքում։ Օգտագործեք ձեր հեռախոսը։"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Կանխադրված"</string>
<string name="default_card_name" msgid="9198284935962911468">"ՔԱՐՏ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 2f2e524..882fb41 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID penelepon diatur default ke tidak dibatasi. Panggilan selanjutnya: Tidak dibatasi"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Layanan tidak diperlengkapi."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak dapat mengubah setelan ID penelepon."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Mengalihkan data seluler ke <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Anda dapat mengubahnya kapan saja di Setelan"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Tidak ada layanan data seluler"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Panggilan darurat tidak tersedia"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tidak ada layanan panggilan suara"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Memungkinkan aplikasi membuat bagian dari dirinya sendiri terus-menerus berada dalam memori. Izin ini dapat membatasi memori yang tersedia untuk aplikasi lain sehingga menjadikan ponsel lambat."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"jalankan layanan di latar depan"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Mengizinkan aplikasi menggunakan layanan di latar depan."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mengukur ruang penyimpanan aplikasi"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Mengizinkan apl mengambil kode, data, dan ukuran temboloknya"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ubah setelan sistem"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikasi ini dapat merekam audio menggunakan mikrofon saat aplikasi sedang digunakan."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"merekam audio di latar belakang"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikasi ini dapat merekam audio menggunakan mikrofon kapan saja."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"kirimkan perintah ke SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Mengizinkan aplikasi mengirim perintah ke SIM. Ini sangat berbahaya."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"kenali aktivitas fisik"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Mengizinkan aplikasi membaca file video dari penyimpanan bersama."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"membaca file gambar dari penyimpanan bersama"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Mengizinkan aplikasi membaca file gambar dari penyimpanan bersama."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"membaca file gambar dan video yang dipilih dari penyimpanan bersama"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Mengizinkan aplikasi membaca file gambar dan video yang Anda pilih dari penyimpanan bersama."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"memodifikasi atau menghapus konten penyimpanan bersama Anda"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Mengizinkan aplikasi menulis konten penyimpanan bersama Anda."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"lakukan/terima panggilan SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTAL"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TETAP BUKA"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplikasi berbahaya terdeteksi"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Izinkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log perangkat?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Izinkan akses satu kali"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Jangan izinkan"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda.\n\nPelajari lebih lanjut di g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Jangan tampilkan lagi"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ingin menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Panggilan dan notifikasi akan bergetar"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Tidak dapat mengakses kamera ponsel dari <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Tidak dapat mengakses kamera tablet dari <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Konten ini tidak dapat diakses saat melakukan streaming. Coba di ponsel."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Default sistem"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTU <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 82a1b66..919276e 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Númerabirting er sjálfgefið án takmarkana. Næsta símtal: Án takmarkana"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Þjónustu ekki útdeilt."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Þú getur ekki breytt stillingu númerabirtingar."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Skipt yfir í farsímagögn hjá <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Þú getur breytt þessu hvenær sem er í stillingum"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Engin gagnaþjónusta fyrir farsíma"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Neyðarsímtöl eru ekki í boði"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Símtöl eru ekki í boði"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Leyfir forriti að gera hluta sjálfs sín varanlega í minni. Þetta getur takmarkað það minni sem býðst öðrum forritum og þannig hægt á símanum."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"keyra þjónustu í forgrunni"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Leyfir forritinu að nota þjónustu sem er í forgrunni."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mæla geymslurými forrits"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Leyfir forriti að sækja stærðir kóða, gagna og skyndiminnis síns."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"breyta kerfisstillingum"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Þetta forrit getur tekið upp hljóð með hljóðnemanum meðan forritið er í notkun."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"taka upp hljóð í bakgrunni"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Þetta forrit getur tekið upp hljóð með hljóðnemanum hvenær sem er."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"senda skipanir til SIM-kortsins"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Leyfir forriti að senda SIM-kortinu skipanir. Þetta er mjög hættulegt."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"greina hreyfingu"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Leyfir forritinu að lesa myndskeiðaskrár úr samnýtta geymslurýminu þínu."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lesa myndskrár úr samnýttu geymslurými"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Leyfir forritinu að lesa myndskrár úr samnýtta geymslurýminu þínu."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lesa mynd- og myndskeiðaskrár sem notendur velja úr samnýttu geymslurými"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Leyfir forritinu að lesa mynd- og myndskeiðaskrár sem þú velur úr samnýtta geymslurýminu þínu."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"breyta eða eyða innihaldi samnýtta geymslurýmisins"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Leyfir forriti að skrifa í innihald samnýtta geymslurýmisins."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"hringja/svara SIP-símtölum"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"FJARLÆGJA"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPNA SAMT"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Skaðlegt forrit fannst"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Veita <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aðgang að öllum annálum í tækinu?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leyfa aðgang í eitt skipti"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ekki leyfa"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu.\n\nNánar á g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ekki sýna aftur"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Breyta"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Titringur er virkur fyrir símtöl og tilkynningar"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ekki er hægt að opna myndavél símans úr <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ekki er hægt að opna myndavél spjaldtölvunnar úr <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ekki er hægt að opna þetta á meðan streymi stendur yfir. Prófaðu það í símanum í staðinn."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Sjálfgildi kerfis"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index b499eea..beb15d2 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID chiamante generalmente non limitato. Prossima chiamata: non limitato"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizio non fornito."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Non è possibile modificare l\'impostazione ID chiamante."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"I dati sono stati trasferiti a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Puoi modificare questa opzione in qualsiasi momento in Impostazioni"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nessun servizio dati mobile"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chiamate di emergenza non disponibili"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nessun servizio di telefonia"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Consente all\'applicazione di rendere persistenti in memoria alcune sue parti. Ciò può limitare la memoria disponibile per altre applicazioni, rallentando il telefono."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"esecuzione servizio in primo piano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Consente all\'app di utilizzare i servizi in primo piano."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"calcolo spazio di archiviazione applicazioni"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Consente all\'applicazione di recuperare il suo codice, i suoi dati e le dimensioni della cache"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modifica delle impostazioni di sistema"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Questa app può registrare audio tramite il microfono mentre l\'app è in uso."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Registrazione di audio in background"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Questa app può registrare audio tramite il microfono in qualsiasi momento."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"invio di comandi alla SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Consente all\'app di inviare comandi alla SIM. Questo è molto pericoloso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"riconoscimento dell\'attività fisica"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Consente all\'app di leggere i file video dal tuo spazio di archiviazione condiviso."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"Lettura dei file immagine dallo spazio di archiviazione condiviso"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Consente all\'app di leggere i file immagine dal tuo spazio di archiviazione condiviso."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"Lettura dei file immagine e video selezionati dall\'utente dallo spazio di archiviazione condiviso"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Consente all\'app di leggere i file immagine e video che selezioni dal tuo spazio di archiviazione condiviso."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"Modifica/eliminazione dei contenuti dell\'archivio condiviso"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Consente all\'app di modificare i contenuti del tuo archivio condiviso."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"invio/ricezione di chiamate SIP"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DISINSTALLA"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"APRI COMUNQUE"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"App dannosa rilevata"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Consentire all\'app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> di accedere a tutti i log del dispositivo?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Consenti accesso una tantum"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Non consentire"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo.\n\nScopri di più all\'indirizzo g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Non mostrare più"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"L\'app <xliff:g id="APP_0">%1$s</xliff:g> vuole mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Modifica"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"La vibrazione sarà attiva per chiamate e notifiche"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossibile accedere alla fotocamera del telefono dal tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossibile accedere alla fotocamera del tablet dal tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Impossibile accedere a questi contenuti durante lo streaming. Prova a usare il telefono."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Predefinita di sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"SCHEDA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 2ef4934..34f4bee 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -397,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"מאפשרת לאפליקציה להפוך חלקים ממנה לקבועים בזיכרון. הפעולה הזו עשויה להגביל את הזיכרון הזמין לאפליקציות אחרות ולהאט את פעולת הטלפון."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"הפעלת שירות בחזית"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"מדידת נפח האחסון של אפליקציות"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"מאפשרת לאפליקציה לאחזר את הקוד, הנתונים, ואת גודל קובצי המטמון שלה"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"שינוי הגדרות מערכת"</string>
@@ -449,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"האפליקציה הזו יכולה להשתמש במיקרופון כדי להקליט אודיו כאשר היא בשימוש."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"הקלטת אודיו ברקע"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"האפליקציה הזו יכולה להשתמש במיקרופון כדי להקליט אודיו בכל זמן."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"שליחת פקודות אל ה-SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"מאפשרת לאפליקציה לשלוח פקודות ל-SIM. זוהי הרשאה מסוכנת מאוד."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"זיהוי של פעילות גופנית"</string>
@@ -700,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"מאפשרת לאפליקציה לקרוא קובצי וידאו מתוך האחסון המשותף."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"קריאה של קובצי תמונה מתוך האחסון המשותף"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"מאפשרת לאפליקציה לקרוא קובצי תמונה מתוך האחסון המשותף."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"קריאה של קובצי הווידאו והתמונה שנבחרו על ידי המשתמש מתוך נפח האחסון המשותף"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ההרשאה מאפשרת לאפליקציה לקרוא קובצי וידאו ותמונה שבחרת מתוך נפח האחסון המשותף."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"שינוי או מחיקה של תוכן האחסון המשותף שלך"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"מאפשרת לאפליקציה לכתוב את התוכן של האחסון המשותף."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"ביצוע/קבלה של שיחות SIP"</string>
@@ -1252,7 +1306,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"מתבצעת הפעלה של אפליקציות."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"תהליך האתחול בשלבי סיום."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"לחצת על לחצן ההפעלה – בדרך כלל הפעולה הזו מכבה את המסך.\n\nעליך לנסות להקיש בעדינות במהלך ההגדרה של טביעת האצבע שלך."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"כדי לסיים את ההגדרה צריך לכבות את המסך"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"לסיום ההגדרה, יש לכבות את המסך"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"השבתה"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"להמשיך לאמת את טביעת האצבע שלך?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"לחצת על לחצן ההפעלה – בדרך כלל הפעולה הזו מכבה את המסך.\n\nעליך לנסות להקיש בעדינות כדי לאמת את טביעת האצבע שלך."</string>
@@ -2050,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"הסרת התקנה"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"לפתוח בכל זאת"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"אותרה אפליקציה מזיקה"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"לתת לאפליקציה <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> הרשאת גישה לכל יומני המכשיר?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"הרשאת גישה חד-פעמית"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"אין אישור"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל יומני המכשיר רק לאפליקציות מהימנות. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל היומנים של המכשיר רק לאפליקציות שסומכים עליהן. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך.\n\nמידע נוסף זמין בכתובת g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"אין להציג שוב"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> רוצה להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"עריכה"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"שיחות והודעות ירטטו"</string>
@@ -2296,6 +2344,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"לא ניתן לגשת למצלמה של הטלפון מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"לא ניתן לגשת למצלמה של הטאבלט מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"אי אפשר לגשת לתוכן המאובטח הזה בזמן סטרימינג. במקום זאת, אפשר לנסות בטלפון."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"אי אפשר להציג תמונה בתוך תמונה בזמן סטרימינג"</string>
<string name="system_locale_title" msgid="711882686834677268">"ברירת המחדל של המערכת"</string>
<string name="default_card_name" msgid="9198284935962911468">"כרטיס <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 53fa057..da7e387 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"既定: 発信者番号通知、次の発信: 通知"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"提供可能なサービスがありません。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"発信者番号の設定は変更できません。"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"データが <xliff:g id="CARRIERDISPLAY">%s</xliff:g> に切り替わりました"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"これは [設定] でいつでも変更できます"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"モバイルデータ サービスのブロック"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"緊急通報のブロック"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"音声通話サービス停止"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"アプリにその一部をメモリに常駐させることを許可します。これにより他のアプリが使用できるメモリが制限されるため、モバイル デバイスの動作が遅くなることがあります。"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"フォアグラウンド サービスの実行"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"フォアグラウンド サービスの使用をアプリに許可します。"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"アプリのストレージ容量の計測"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"アプリのコード、データ、キャッシュサイズを取得することをアプリに許可します"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"システム設定の変更"</string>
@@ -449,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"このアプリは、ユーザーがアプリを使用している場合にマイクを使用して音声を録音できます。"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"バックグラウンドでの音声の録音"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"このアプリは、いつでもマイクを使用して音声を録音できます。"</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"アプリ ウィンドウのスクリーン キャプチャの検出"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"アプリの使用中にスクリーンショットが撮影されると、このアプリに通知が届きます。"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIMへのコマンド送信"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMにコマンドを送信することをアプリに許可します。この許可は非常に危険です。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"身体活動の認識"</string>
@@ -700,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"共有ストレージからの動画ファイルの読み取りをアプリに許可します。"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"共有ストレージからの画像ファイルの読み取り"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"共有ストレージからの画像ファイルの読み取りをアプリに許可します。"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ユーザーが選択した画像と動画ファイルの共有ストレージからの読み取り"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"選択した画像と動画ファイルを共有ストレージから読み取ることをアプリに許可します。"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"共有ストレージのコンテンツの変更または削除"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"共有ストレージのコンテンツの書き込みをアプリに許可します。"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP通話の発着信"</string>
@@ -2050,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"アンインストール"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"開く"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"有害なアプリが検出されました"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> にすべてのデバイスログへのアクセスを許可しますか?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"1 回限りのアクセスを許可"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"許可しない"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合も、このアプリはアプリ独自のログにアクセスできます。また、デバイスのメーカーもデバイスの一部のログや情報にアクセスできる可能性があります。"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合でも、このアプリはアプリ独自のログにアクセスできます。また、デバイスの製造メーカーもデバイスの一部のログや情報にアクセスできる可能性があります。\n\n詳しくは、g.co/android/devicelogs をご覧ください。"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"次回から表示しない"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」が「<xliff:g id="APP_2">%2$s</xliff:g>」のスライスの表示をリクエストしています"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"編集"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"着信や通知をバイブレーションで知らせます"</string>
@@ -2296,6 +2340,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> からスマートフォンのカメラにアクセスできません"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> からタブレットのカメラにアクセスできません"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ストリーミング中はアクセスできません。スマートフォンでのアクセスをお試しください。"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ストリーミング中はピクチャー イン ピクチャーを表示できません"</string>
<string name="system_locale_title" msgid="711882686834677268">"システムのデフォルト"</string>
<string name="default_card_name" msgid="9198284935962911468">"カード <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 57ff75a..664a25c 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: არ არის დაფარული."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"სერვისი არ არის მიწოდებული."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"არ შეგიძლიათ აბონენტის ID პარამეტრების შეცვლა."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"მონაცემები გადართულია <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-ზე"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"ამის შეცვლა ნებისმიერ დროს შეგიძლიათ პარამეტრებში"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"მობილური ინტერნეტის სერვისი არ არის"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"გადაუდებელი ზარი მიუწვდომელია"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ხმოვანი ზარების სერვისი არ არის"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"აპს შეეძლება, საკუთარი ნაწილები მუდმივად ჩაწეროს მეხსიერებაში. ეს შეზღუდავს მეხსიერების ხელმისაწვდომობას სხვა აპებისთვის და შეანელებს ტელეფონს."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"წინა პლანის სერვისის გაშვება"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"აპს შეეძლება, გამოიყენოს წინა პლანის სერვისები."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"აპის მეხსიერების სივრცის გაზომვა"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"აპს შეეძლება, მოიპოვოს თავისი კოდი, მონაცემები და ქეშის ზომები."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"სისტემის პარამეტრების შეცვლა"</string>
@@ -449,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ამ აპს გამოყენების დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ფონურად ჩაწერს აუდიოს"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ამ აპს ნებისმიერ დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"აპის ფანჯრების ეკრანის აღბეჭდვის გამოვლენა"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ეს აპი მიიღებს შეტყობინებას, როდესაც ეკრანის ანაბეჭდის გადაღება აპის გამოყენების პროცესში მოხდება."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ბრძანებების SIM-ზე გაგზავნა"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"აპისთვის ნების დართვა გაუგზავნოს ბრძანებები SIM-ბარათს. ეს ძალიან საშიშია."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ფიზიკური აქტივობის ამოცნობა"</string>
@@ -700,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"საშუალებას აძლევს აპს, წაიკითხოს ვიდეო ფაილები თქვენი ზიარი მეხსიერებიდან."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"სურათების ფაილების წაკითხვა ზიარი მეხსიერებიდან"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"საშუალებას აძლევს აპს, წაიკითხოს სურათის ფაილები თქვენი ზიარი მეხსიერებიდან."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"წაიკითხეთ მომხმარებლის მიერ არჩეული სურათი და ვიდეო ფაილები საზიარო მეხსიერებიდან"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"აპს შეეძლება წაიკითხოს სურათი და ვიდეო ფაილები, რომლებიც არჩეულია თქვენი საზიარო მეხსიერებიდან."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"თქვენი ზიარი მეხსიერების შიგთავსის შეცვლა ან წაშლა"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"საშუალებას აძლევს აპს, ჩაწეროს თქვენი ზიარი მეხსიერების შიგთავსი."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP ზარების წამოწყება/მიღება"</string>
@@ -2050,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"დეინსტალაცია"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"მაინც გახსნა"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"აღმოჩენილია საზიანო აპი"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"გსურთ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-ს მიანიჭოთ მოწყობილობის ყველა ჟურნალზე წვდომა?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ერთჯერადი წვდომის დაშვება"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"არ დაიშვას"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა თქვენს ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა საკუთარ ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა.\n\n შეიტყვეთ მეტი g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"აღარ გამოჩნდეს"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>-ს სურს, გაჩვენოთ <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"რედაქტირება"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ზარების და შეტყობინებების მიღებისას ვიბრაცია ჩაირთვება"</string>
@@ -2296,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ტელეფონის კამერაზე წვდომა ვერ მოხერხდა თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ტაბლეტის კამერაზე წვდომა ვერ მოხერხდა თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"მასზე წვდომის მიᲦება შეუძლებელია სტრიმინგის დროს. ცადეთ ტელეფონიდან."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"სისტემის ნაგულისხმევი"</string>
<string name="default_card_name" msgid="9198284935962911468">"ბარათი <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 8f5b1c0..14da390 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелмеген"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Қызмет ұсынылмаған."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Қоңырау шалушы идентификаторы параметрін өзгерту мүмкін емес."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Деректер <xliff:g id="CARRIERDISPLAY">%s</xliff:g> операторына ауыстырылды"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Бұны кез келген уақытта \"Параметрлер\" бөлімінен өзгертуге болады."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильдік интернет қызметі жоқ"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Жедел қызметке қоңырау шалу қолжетімді емес"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дауыстық қоңыраулар қызметі жоқ"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Қолданбаға өзінің бөліктерін жадта бекіндіру мүмкіндігін береді. Бұл басқа қолданбалардың жадқа қол жетімділігін шектеп, телефонды баяулатуы мүмкін."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"басымдылығы жоғары қызметті іске қосу"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Қолданбаға басымдылығы жоғары қызметтерді пайдалануға рұқсат береді."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"қолданба жадындағы бос орынды өлшеу"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Қолданбаға оның кодын, деректерін және кэш өлшемдерін шығарып алуға рұқсат береді"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"жүйе параметрлерін өзгерту"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Бұл қолданба жұмыс барысында микрофон арқылы аудиомазмұн жаза алады."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Фондық режимде аудиомазмұн жазу"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Бұл қолданба кез келген уақытта микрофон арқылы аудиомазмұн жаза алады."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM картасына пәрмендер жіберу"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Қолданбаға SIM картасына пәрмен жіберу мүмкіндігін береді. Бұл өте қауіпті."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"физикалық әрекетті тану"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Қолданбаға ортақ жадтың бейнефайлдарын оқуға мүмкіндік береді."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ортақ жадтың кескін файлдарын оқу"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Қолданбаға ортақ жадтың кескін файлдарын оқуға мүмкіндік береді."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"Пайдаланушының ортақ жадтағы сурет файлдарын және бейнефайлдарын оқу"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Қолданбаға ортақ жадтың сурет файлдарын және бейнефайлдарын оқуға мүмкіндік береді."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ортақ жадтың мазмұнын өзгерту немесе жою"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Қолданбаға ортақ жадтың мазмұнын жазуға мүмкіндік береді."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP қоңырауларын шалу/қабылдау"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ЖОЮ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"БӘРІБІР АШУ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Зиянды қолданба анықталды"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> қолданбасына барлық құрылғының журналын пайдалануға рұқсат берілсін бе?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бір реттік пайдалану рұқсатын беру"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Рұқсат бермеу"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін.\n\nТолық ақпарат: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Қайта көрсетілмесін"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасы <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсеткісі келеді"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Өзгерту"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Қоңыраулар мен хабарландырулардың вибрациясы болады"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан телефон камерасын пайдалану мүмкін емес."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан планшет камерасын пайдалану мүмкін емес."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Трансляция кезінде мазмұнды көру мүмкін емес. Оның орнына телефоннан көріңіз."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Жүйенің әдепкі параметрі"</string>
<string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g>-КАРТА"</string>
</resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 1eae9db..fc1a49c 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"មិនបានដាក់កម្រិតលំនាំដើមលេខសម្គាល់អ្នកហៅ។ ការហៅបន្ទាប់៖ មិនបានដាក់កម្រិត។"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"មិនបានផ្ដល់សេវាកម្ម។"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"អ្នកមិនអាចប្ដូរការកំណត់លេខសម្គាល់អ្នកហៅបានទេ។"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"បានប្ដូរទិន្នន័យទៅ <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"អ្នកអាចផ្លាស់ប្ដូរលក្ខណៈនេះនៅពេលណាក៏បាននៅក្នុងការកំណត់"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"គ្មានសេវាកម្មទិន្នន័យចល័តទេ"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ការហៅបន្ទាន់មិនអាចប្រើបានទេ"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"គ្មានសេវាកម្មជាសំឡេងទេ"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ឲ្យកម្មវិធី ធ្វើជាផ្នែកអចិន្ត្រៃយ៍នៃខ្លួនក្នុងអង្គចងចាំ។ វាអាចកម្រិតអង្គចងចាំអាចប្រើបាន ដើម្បីធ្វើឲ្យកម្មវិធីផ្សេងធ្វើឲ្យទូរស័ព្ទរបស់អ្នកយឺត។"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខ"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខ។"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"វាស់ទំហំការផ្ទុកកម្មវិធី"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ឲ្យកម្មវិធីទៅយកកូដ ទិន្នន័យ និងទំហំឃ្លាំងសម្ងាត់របស់វា"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"កែការកំណត់ប្រព័ន្ធ"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"កម្មវិធីនេះអាចថតសំឡេងដោយប្រើមីក្រូហ្វូន នៅពេលកំពុងប្រើប្រាស់កម្មវិធី។"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ថតសំឡេងនៅផ្ទៃខាងក្រោយ"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"កម្មវិធីនេះអាចថតសំឡេងដោយប្រើមីក្រូហ្វូនបានគ្រប់ពេល។"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"ផ្ញើពាក្យបញ្ជាទៅស៊ីមកាត"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ឲ្យកម្មវិធីផ្ញើពាក្យបញ្ជាទៅស៊ីមកាត។ វាគ្រោះថ្នាក់ណាស់។"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ស្គាល់សកម្មភាពរាងកាយ"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"អនុញ្ញាតឱ្យកម្មវិធីអានឯកសារវីដេអូពីទំហំផ្ទុករួមរបស់អ្នក។"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"អានឯកសាររូបភាពពីទំហំផ្ទុករួម"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"អនុញ្ញាតឱ្យកម្មវិធីអានឯកសាររូបភាពពីទំហំផ្ទុករួមរបស់អ្នក។"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"អានឯកសាររូបភាព និងវីដេអូដែលអ្នកប្រើប្រាស់ជ្រើសរើសពីទំហំផ្ទុករួម"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"អនុញ្ញាតឱ្យកម្មវិធីអានឯកសាររូបភាព និងវីដេអូដែលអ្នកជ្រើសរើសពីទំហំផ្ទុករួមរបស់អ្នក។"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"កែប្រែ ឬលុបខ្លឹមសារនៃទំហំផ្ទុករួមរបស់អ្នក"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"អនុញ្ញាតឱ្យកម្មវិធីសរសេរខ្លឹមសារនៃទំហំផ្ទុករួមរបស់អ្នក។"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"បង្កើត/ទទួល ការហៅ SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"លុប"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"មិនអីទេ បើកចុះ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"បានរកឃើញកម្មវិធីដែលបង្កគ្រោះថ្នាក់"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"អនុញ្ញាតឱ្យ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ឬ?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"អនុញ្ញាតឱ្យចូលប្រើម្ដង"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"មិនអនុញ្ញាត"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុនផលិតឧបករណ៍របស់អ្នកប្រហែលជានៅតែអាចចូលប្រើកំណត់ហេតុ ឬព័ត៌មានមួយចំនួននៅលើឧបករណ៍របស់អ្នកបានដដែល។"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុនផលិតឧបករណ៍របស់អ្នកប្រហែលជានៅតែអាចចូលប្រើកំណត់ហេតុ ឬព័ត៌មានមួយចំនួននៅលើឧបករណ៍របស់អ្នកបានដដែល។\n\nស្វែងយល់បន្ថែមតាមរយៈ g.co/android/devicelogs។"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"កុំបង្ហាញម្ដងទៀត"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ចង់បង្ហាញស្ថិតិប្រើប្រាស់របស់ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"កែ"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ការហៅទូរសព្ទ និងការជូនដំណឹងនឹងញ័រ"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"មិនអាចចូលប្រើកាមេរ៉ាទូរសព្ទពី <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នកបានទេ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"មិនអាចចូលប្រើកាមេរ៉ាថេប្លេតពី <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នកបានទេ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"មិនអាចចូលប្រើប្រាស់ខ្លឹមសារនេះបានទេ ពេលផ្សាយ។ សូមសាកល្បងប្រើនៅលើទូរសព្ទរបស់អ្នកជំនួសវិញ។"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"លំនាំដើមប្រព័ន្ធ"</string>
<string name="default_card_name" msgid="9198284935962911468">"កាត <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 821138a..7ca2dff 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿಲ್ಲ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ಸೇವೆಯನ್ನು ಪೂರೈಸಲಾಗಿಲ್ಲ."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ನೀವು ಕಾಲರ್ ID ಸೆಟ್ಟಿಂಗ್ ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> ಗೆ ಡೇಟಾವನ್ನು ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"ನೀವು ಇದನ್ನು ಯಾವುದೇ ಸಮಯದಲ್ಲಿಯೂ ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಬದಲಿಸಬಹುದು"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ಮೊಬೈಲ್ ಡೇಟಾ ಸೇವೆಯಿಲ್ಲ"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ತುರ್ತು ಕರೆ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ಧ್ವನಿ ಸೇವೆಯಿಲ್ಲ"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ಸ್ಮರಣೆಯಲ್ಲಿ ನಿರಂತರವಾಗಿ ತನ್ನದೇ ಭಾಗಗಳನ್ನು ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ಫೋನ್ ಕಾರ್ಯವನ್ನು ನಿಧಾನಗೊಳಿಸುವುದರ ಮೂಲಕ ಇತರ ಅಪ್ಲಿಕೇಶನ್ಗಳಿಗೆ ಲಭ್ಯವಿರುವ ಸ್ಮರಣೆಯನ್ನು ಮಿತಿಗೊಳಿಸುತ್ತದೆ."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡಿ."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ಅಪ್ಲಿಕೇಶನ್ ಸಂಗ್ರಹ ಸ್ಥಳವನ್ನು ಅಳೆಯಿರಿ"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ಅದರ ಕೋಡ್, ಡೇಟಾ, ಮತ್ತು ಕ್ಯಾಷ್ ಗಾತ್ರಗಳನ್ನು ಹಿಂಪಡೆಯಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ಸಿಸ್ಟಂ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಮಾರ್ಪಡಿಸಿ"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ಆ್ಯಪ್ ಬಳಕೆಯಲ್ಲಿರುವಾಗ ಈ ಆ್ಯಪ್ ಮೈಕ್ರೊಫೋನ್ ಬಳಸಿ ಆಡಿಯೊವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಆಡಿಯೊವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ಈ ಆ್ಯಪ್ ಮೈಕ್ರೋಫೋನ್ ಬಳಸುವ ಮೂಲಕ ಯಾವುದೇ ಸಮಯದಲ್ಲಾದರೂ ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"ಸಿಮ್ಗೆ ಆಜ್ಞೆಗಳನ್ನು ಕಳುಹಿಸಿ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ಸಿಮ್ ಗೆ ಆದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು ತುಂಬಾ ಅಪಾಯಕಾರಿ."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ದೈಹಿಕ ಚಟುವಟಿಕೆಯನ್ನು ಗುರುತಿಸಿ"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ವೀಡಿಯೊ ಫೈಲ್ಗಳನ್ನು ಓದಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ಚಿತ್ರದ ಫೈಲ್ಗಳನ್ನು ಓದಿ"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ಚಿತ್ರದ ಫೈಲ್ಗಳನ್ನು ಓದಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ಬಳಕೆದಾರರು ಆಯ್ಕೆಮಾಡಿದ ಚಿತ್ರ ಮತ್ತು ವೀಡಿಯೊ ಫೈಲ್ಗಳನ್ನು ಓದಿರಿ"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ನೀವು ಆಯ್ಕೆಮಾಡಿದ ಚಿತ್ರ ಮತ್ತು ವೀಡಿಯೊ ಫೈಲ್ಗಳನ್ನು ಓದಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯ ವಿಷಯಗಳನ್ನು ಮಾರ್ಪಡಿಸಿ ಅಥವಾ ಅಳಿಸಿ"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯ ವಿಷಯಗಳನ್ನು ಬರೆಯಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"ಎಸ್ಐಪಿ ಕರೆಗಳನ್ನು ಮಾಡಿ/ಸ್ವೀಕರಿಸಿ"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ಹೇಗಿದ್ದರೂ ತೆರೆಯಿರಿ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ಅಪಾಯಕಾರಿ ಅಪ್ಲಿಕೇಶನ್ ಕಂಡುಬಂದಿದೆ"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"ಎಲ್ಲಾ ಸಾಧನದ ಲಾಗ್ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ಒಂದು ಬಾರಿಯ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ಅನುಮತಿಸಬೇಡಿ"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್ಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ನೀವು ಈ ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್ಗಳನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಲು ಈಗಲೂ ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ನೀವು ಈ ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದು. ಹಾಗಿದ್ದರೂ, ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಬಹುದು.\n\ng.co/android/devicelogs ನಲ್ಲಿ ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್ಗಳನ್ನು <xliff:g id="APP_0">%1$s</xliff:g> ತೋರಿಸಲು ಬಯಸಿದೆ"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ಎಡಿಟ್"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ಕರೆಗಳು ಮತ್ತು ಅಧಿಸೂಚನೆಗಳು ವೈಬ್ರೇಟ್ ಆಗುತ್ತವೆ"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ಮೂಲಕ ಫೋನ್ನ ಕ್ಯಾಮರಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ಮೂಲಕ ಟ್ಯಾಬ್ಲೆಟ್ನ ಕ್ಯಾಮರಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ಸ್ಟ್ರೀಮ್ ಮಾಡುವಾಗ ಇದನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಅದರ ಬದಲು ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"ಸಿಸ್ಟಂ ಡೀಫಾಲ್ಟ್"</string>
<string name="default_card_name" msgid="9198284935962911468">"ಕಾರ್ಡ್ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 5f3c7ca..a3a2f22 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"서비스가 준비되지 않았습니다."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"발신자 번호 설정을 변경할 수 없습니다."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> 이동통신사로 데이터가 변경됨"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"언제든지 설정에서 변경할 수 있습니다."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"모바일 데이터 서비스가 차단됨"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"긴급 전화를 사용할 수 없음"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"음성 서비스를 이용할 수 없음"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"앱이 그 일부분을 영구적인 메모리로 만들 수 있도록 허용합니다. 이렇게 하면 다른 앱이 사용할 수 있는 메모리를 제한하여 휴대전화의 속도를 저하시킬 수 있습니다."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"포그라운드 서비스 실행"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"앱에서 포그라운드 서비스를 사용하도록 허용합니다."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"앱 저장공간 계산"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"앱이 해당 코드, 데이터 및 캐시 크기를 검색할 수 있도록 허용합니다."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"시스템 설정 수정"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"앱을 사용하는 동안 앱에서 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"백그라운드에서 오디오 녹음"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"언제든지 앱에서 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM 카드로 명령 전송"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"앱이 SIM에 명령어를 전송할 수 있도록 허용합니다. 이 기능은 매우 신중히 허용해야 합니다."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"신체 활동 확인"</string>
@@ -598,7 +648,7 @@
<string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"지문을 등록할 때마다 손가락을 조금씩 이동하세요"</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
- <string name="fingerprint_error_not_match" msgid="4599441812893438961">"지문이 인식되지 않습니다."</string>
+ <string name="fingerprint_error_not_match" msgid="4599441812893438961">"지문이 인식되지 않았습니다."</string>
<string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"지문을 인식할 수 없습니다."</string>
<string name="fingerprint_authenticated" msgid="2024862866860283100">"지문이 인증됨"</string>
<string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"얼굴이 인증되었습니다"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"앱이 공유 저장소에서 동영상 파일을 읽도록 허용합니다."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"공유 저장소에서 이미지 파일 읽기"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"앱이 공유 저장소에서 이미지 파일을 읽도록 허용합니다."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"공유 저장소에서 사용자가 선택한 이미지 및 동영상 파일 읽기"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"앱이 공유 저장소에서 내가 선택한 이미지와 동영상 파일을 읽을 수 있도록 허용합니다."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"공유 저장공간의 콘텐츠 수정 또는 삭제"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"앱이 공유 저장공간의 콘텐츠에 쓰도록 허용합니다."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP 통화 발신/수신"</string>
@@ -1253,7 +1305,7 @@
<string name="android_upgrading_complete" msgid="409800058018374746">"부팅 완료"</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"전원 버튼을 눌렀습니다. 이러면 보통 화면이 꺼집니다.\n\n지문 설정 중에 가볍게 탭하세요."</string>
<string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"설정을 완료하려면 화면을 끄세요"</string>
- <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"사용 중지"</string>
+ <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"화면 끄기"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"지문 인증을 계속할까요?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"전원 버튼을 눌렀습니다. 이러면 보통 화면이 꺼집니다.\n\n지문을 인식하려면 화면을 가볍게 탭하세요."</string>
<string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"화면 끄기"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"제거"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"열기"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"유해한 앱 감지됨"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>에서 모든 기기 로그에 액세스하도록 허용하시겠습니까?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"일회성 액세스 허용"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"허용 안함"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"기기 로그에 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그는 민감한 정보를 포함할 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 일부 로그 또는 기기 내 정보에 액세스할 수도 있습니다."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"기기 로그에는 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그에 민감한 정보가 포함될 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도, 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 기기 내 일부 로그 또는 정보에 액세스할 수도 있습니다.\n\ng.co/android/devicelogs에서 자세히 알아보세요."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"다시 표시 안함"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하려고 합니다"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"수정"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"전화 및 알림이 오면 진동이 사용됩니다."</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"사용자의 <xliff:g id="DEVICE">%1$s</xliff:g>에서 휴대전화 카메라에 액세스할 수 없습니다."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"사용자의 <xliff:g id="DEVICE">%1$s</xliff:g>에서 태블릿 카메라에 액세스할 수 없습니다."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"스트리밍 중에는 액세스할 수 없습니다. 대신 휴대전화에서 시도해 보세요."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"시스템 기본값"</string>
<string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> 카드"</string>
</resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 6e7b355..92943d0 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелбейт"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Кызмат камсыздалган эмес."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары жөндөөлөрүн өзгөртө албайсыз."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилдик Интернет <xliff:g id="CARRIERDISPLAY">%s</xliff:g> которулду"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Бул функциянын параметрлерин \"Тууралоо\" бөлүмүнөн өзгөртө аласыз"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобилдик Интернет кызматы жок"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Шашылыш чалуу бөгөттөлгөн"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Аудио чалуу кызматы бөгөттөлгөн"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Колдонмого өзүнүн бөлүктөрүн эстутумда туруктуу кармоого уруксат берет. Бул эстутумдун башка колдонмолорго жетиштүүлүгүн чектеши жана телефондун иштешин жайлатышы мүмкүн."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"активдүү кызматты иштетүү"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Колдонмолорго алдынкы пландагы кызматтарды колдонууга уруксат берет."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"колдонмо сактагычынын мейкиндигин өлчөө"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Колдонмого өз кодун, дайындарын жана кэш өлчөмдөрүн түшүрүп алуу мүмкүнчүлүгүн берет"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"система тууралоолорун өзгөртүү"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Бул колдонмо иштеп жатканда микрофон менен аудио файлдарды жаздыра алат."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Фондо аудио жаздыруу"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Бул колдонмо каалаган убакта микрофон менен аудио файлдарды жаздыра алат."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM-картага буйруктарды жөнөтүү"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Колдонмого SIM-картага буйруктарды жөнөтүү мүмкүнчүлүгүн берет. Бул абдан кооптуу."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"Кыймыл-аракетти аныктоо"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Колдонмого жалпы сактагычыңыздагы видеолорду окуу мүмкүнчүлүгүн берет."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"жалпы сактагычтагы сүрөт файлдарды окуу"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Колдонмого жалпы сактагычыңыздагы сүрөт файлдарды окуу мүмкүнчүлүгүн берет."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"жалпы сактагычтагы колдонуучу тандаган сүрөт жана видео файлдарын окуу"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Колдонмого жалпы сактагычыңыздагы өзүңүз тандаган сүрөт жана видео файлдарын окуу мүмкүнчүлүгүн берет."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"жалпы сактагычыңыздын мазмунун өзгөртүү же жок кылуу"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Колдонмого жалпы сактагычыңыздын мазмунун жазуу мүмкүнчүлүгүн берет."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP чалуу/чалууну кабыл алуу"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ЧЫГАРЫП САЛУУ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"БААРЫ БИР АЧЫЛСЫН"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Зыянкеч колдонмо аныкталды"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> колдонмосуна түзмөктөгү бардык таржымалдарды жеткиликтүү кыласызбы?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бир жолу жеткиликтүү кылуу"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Жок"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Түзмөктө аткарылган бардык аракеттер түзмөктүн таржымалдарында сакталып калат. Колдонмолор бул таржымалдарды колдонуп, маселелерди оңдошот.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан түзмөктөгү бардык таржымалдарды ишенимдүү колдонмолорго гана пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Түзмөктө жасалган нерселердин баары таржымалга сактала берет. Колдонмолор анын жардамы менен көйгөйлөрдү аныктап, оңдоп турушат.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан ишенимдүү колдонмолорго гана түзмөктөгү бардык таржымалдарды пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет.\n\nКеңири маалымат: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Экинчи көрүнбөсүн"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосу <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөткөнү жатат"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Түзөтүү"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Чалуулар менен билдирмелер дирилдөө режиминде иштейт"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн телефондун камерасына мүмкүнчүлүк жок"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн планшетиңиздин камерасына мүмкүнчүлүк жок"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Муну алып ойнотуу учурунда көрүүгө болбойт. Анын ордуна телефондон кирип көрүңүз."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Системанын демейки параметрлери"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 7a76c2c..0f73d68 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ບໍ່ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ບໍ່ໄດ້ເປີດໃຊ້ບໍລິການ."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ທ່ານບໍ່ສາມາດປ່ຽນແປງການຕັ້ງຄ່າ Caller ID"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"ປ່ຽນໄປໃຊ້ອິນເຕີເນັດມືຖືຂອງ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ແລ້ວ"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"ທ່ານສາມາດປ່ຽນສິ່ງນີ້ຕອນໃດກໍໄດ້ໃນການຕັ້ງຄ່າ"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ບໍ່ມີບໍລິການອິນເຕີເນັດມືຖື"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ບໍ່ສາມາດໃຊ້ການໂທສຸກເສີນໄດ້"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ບໍ່ມີບໍລິການໂທສຽງ"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ອະນຸຍາດໃຫ້ແອັບຯເຮັດໃຫ້ສ່ວນນຶ່ງຂອງຕົນເອງ ຄົງຢູ່ຖາວອນໃນໜ່ວຍຄວາມຈຳ ເຊິ່ງອາດສາມາດ ເຮັດໃຫ້ການນຳໃຊ້ໜ່ວຍຄວາມຈຳຂອງແອັບຯ ອື່ນຖືກຈຳກັດ ສົ່ງຜົນເຮັດໃຫ້ມືຖືຂອງທ່ານເຮັດວຽກຊ້າລົງໄດ້."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ໃຊ້ບໍລິການພື້ນໜ້າ"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ບໍລິການພື້ນໜ້າ."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ກວດສອບພື້ນທີ່ຈັດເກັບຂໍ້ມູນແອັບຯ"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ອະນຸຍາດໃຫ້ແອັບຯດຶງໂຄດ, ຂໍ້ມູນ ແລະຂະໜາດ cache ຂອງມັນໄດ້."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ແກ້ໄຂການຕັ້ງຄ່າລະບົບ"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ແອັບນີ້ສາມາດບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນໃນຂະນະທີ່ກຳລັງໃຊ້ແອັບຢູ່ໄດ້."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ບັນທຶກສຽງໃນພື້ນຫຼັງ"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ແອັບນີ້ສາມາດບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນຕອນໃດກໍໄດ້."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"ສົ່ງຄຳສັ່ງຫາ SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ອະນຸຍາດໃຫ້ແອັບຯສົ່ງຄຳສັ່ງຫາ SIM. ສິ່ງນີ້ອັນຕະລາຍຫຼາຍ."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ຈຳແນກກິດຈະກຳທາງກາຍ"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ອະນຸຍາດໃຫ້ແອັບອ່ານໄຟລ໌ວິດີໂອຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ອ່ານໄຟລ໌ຮູບຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນ"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ອະນຸຍາດໃຫ້ແອັບອ່ານໄຟລ໌ຮູບຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ອ່ານໄຟລ໌ຮູບ ແລະ ວິດີໂອທີ່ຜູ້ໃຊ້ເລືອກຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນ"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ອະນຸຍາດໃຫ້ແອັບອ່ານໄຟລ໌ຮູບ ແລະ ວິດີໂອທີ່ທ່ານເລືອກຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ແກ້ໄຂ ຫຼືລຶບເນື້ອຫາໃນບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ອະນຸຍາດໃຫ້ແອັບຂຽນເນື້ອຫາຕ່າງໆຂອງບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"ຮັບສາຍ/ໂທອອກ ຜ່ານ SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ຖອນການຕິດຕັ້ງ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ຢືນຢັນການເປີດ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ກວດສອບແອັບທີ່ເປັນອັນຕະລາຍ"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"ອະນຸຍາດໃຫ້ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດບໍ?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ອະນຸຍາດການເຂົ້າເຖິງແບບເທື່ອດຽວ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ບໍ່ອະນຸຍາດ"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ບັນທຶກອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດແອັບນີ້ໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ບັນທຶກອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດແອັບນີ້ໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້.\n\nສຶກສາເພີ່ມເຕີມໄດ້ຢູ່ g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ບໍ່ຕ້ອງສະແດງອີກ"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ຕ້ອງການສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ແກ້ໄຂ"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ການໂທ ແລະ ການແຈ້ງເຕືອນຈະສັ່ນ"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ບໍ່ສາມາດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງໂທລະສັບຈາກ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ບໍ່ສາມາດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງແທັບເລັດຈາກ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ບໍ່ສາມາດເຂົ້າເຖິງເນື້ອຫານີ້ໄດ້ໃນຂະນະທີ່ຍັງສະຕຣີມຢູ່. ກະລຸນາລອງຢູ່ໂທລະສັບຂອງທ່ານແທນ."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"ຄ່າເລີ່ມຕົ້ນຂອງລະບົບ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ບັດ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index e0d1fc7..404dbd8 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Skambintojo ID pagal numatytuosius nustatymus yra neapribotas. Kitas skambutis: neapribotas"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Paslauga neteikiama."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Negalima pakeisti skambinančiojo ID nustatymo."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Duomenys perjungti į „<xliff:g id="CARRIERDISPLAY">%s</xliff:g>“"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Galite tai bet kada pakeisti nustatymuose"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Duomenų paslaugos mobiliesiems nėra"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Skambučių pagalbos numeriu paslaugos nėra"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Balso skambučių paslauga neteikiama"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Leidžiama programai savo dalis įrašyti į atmintį. Dėl to gali būti apribota kitomis programomis pasiekiama atmintis ir sulėtėti telefono veikimas."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"vykdyti priekiniame plane veikiančią paslaugą"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Programai leidžiama naudoti priekiniame plane veikiančias paslaugas."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"matuoti programos atmintinės vietą"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Leidžiama programai nuskaityti kodą, duomenis ir talpykloje saugoti dydžius"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"keisti sistemos nustatymus"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Ši programa gali įrašyti garsą naudodama mikrofoną, kol programa naudojama."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"įrašyti garsą fone"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ši programa gali bet kada įrašyti garsą naudodama mikrofoną."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"siųsti komandas į SIM kortelę"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Programai leidžiama siųsti komandas į SIM kortelę. Tai labai pavojinga."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"atpažinti fizinę veiklą"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Leidžiama programai nuskaityti vaizdo įrašo failus iš bendrinamos saugyklos."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"nuskaityti vaizdo failus iš bendrinamos saugyklos"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Leidžiama programai nuskaityti vaizdo failus iš bendrinamos saugyklos."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"nuskaityti naudotojo pasirinktus vaizdo ir vaizdo įrašo failus iš bendrinamos saugyklos"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Leidžiama programai nuskaityti jūsų pasirinktus vaizdo ir vaizdo įrašo failus iš bendrinamos saugyklos."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"keisti / trinti bendr. atm. t."</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Pr. leidž. raš. bendr. atm. t."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"skambinti / priimti SIP skambučius"</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"PAŠALINTI"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VIS TIEK ATIDARYTI"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Aptikta žalinga programa"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Leisti „<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>“ pasiekti visus įrenginio žurnalus?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leisti vienkartinę prieigą"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neleisti"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Įrenginyje įrašoma, kas įvyksta jūsų įrenginyje. Programos gali naudoti šiuos žurnalus, kad surastų ir išspręstų problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Įrenginyje įrašoma, kas jame įvyksta. Programos gali naudoti šiuos žurnalus, kai reikia surasti ir išspręsti problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje.\n\nSužinokite daugiau adresu g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Daugiau neberodyti"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"„<xliff:g id="APP_0">%1$s</xliff:g>“ nori rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Redaguoti"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Skambučiai ir pranešimai vibruos"</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nepavyko pasiekti telefono fotoaparato iš „<xliff:g id="DEVICE">%1$s</xliff:g>“"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nepavyko pasiekti planšetinio kompiuterio fotoaparato iš „<xliff:g id="DEVICE">%1$s</xliff:g>“"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nepavyksta pasiekti perduodant srautu. Pabandykite naudoti telefoną."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Numatytoji sistemos vertė"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORTELĖ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index ec8cb7d..721c17e 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zvanītāja ID noklusējumi ir iestatīti uz Nav ierobežots. Nākamais zvans: nav ierobežots"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Pakalpojums netiek nodrošināts."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Zvanītāja ID iestatījumu nevar mainīt."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Tiek izmantots operatora <xliff:g id="CARRIERDISPLAY">%s</xliff:g> datu savienojums"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Šo opciju jebkurā brīdī var mainīt iestatījumos"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nav pieejams neviens datu pakalpojums mobilajām ierīcēm"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Nav pieejami ārkārtas izsaukumi"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Balss izsaukumu pakalpojums nedarbojas"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ļauj lietotnei nodrošināt atsevišķu tās daļu nepārtrauktu atrašanos atmiņā. Tas var ierobežot pieejamo atmiņas daudzumu citām lietotnēm, tādējādi palēninot tālruņa darbību."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"Aktivizēt priekšplāna pakalpojumu"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Ļauj lietotnei izmantot priekšplāna pakalpojumus."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"noteikt vietas apjomu lietotnes atmiņā"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Ļauj lietotnei izgūt tās koda datus un kešatmiņas izmēru."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"mainīt sistēmas iestatījumus"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Šī lietotne var ierakstīt audio, izmantojot mikrofonu, kamēr lietotne tiek izmantota."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ierakstīt audio fonā"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Šī lietotne var jebkurā brīdī ierakstīt audio, izmantojot mikrofonu."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"Sūtīt komandas SIM kartei"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Ļauj lietotnei sūtīt komandas uz SIM karti. Tas ir ļoti bīstami!"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"noteikt fiziskās aktivitātes"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Ļauj lietotnei lasīt video failus jūsu koplietotajā krātuvē."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lasīt attēlu failus koplietotajā krātuvē"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Ļauj lietotnei lasīt attēlu failus jūsu koplietotajā krātuvē."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lasīt lietotāja atlasītos attēlu un video failus no kopīgās krātuves"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Ļauj lietotnei lasīt attēlu un video failus, ko atlasāt no savas kopīgās krātuves."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"Jūsu kopīgotās krātuves satura pārveidošana vai dzēšana"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Ļauj lietotnei rakstīt jūsu kopīgotās krātuves saturu."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP zvanu veikšana/saņemšana"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ATINSTALĒT"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TIK UN TĀ ATVĒRT"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Konstatēta kaitīga lietotne"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vai atļaujat lietotnei <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> piekļūt visiem ierīces žurnāliem?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Atļaut vienreizēju piekļuvi"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neatļaut"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē.\n\nŠeit varat uzzināt vairāk: g.co/android/devicelogs"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Vairs nerādīt"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Lietotne <xliff:g id="APP_0">%1$s</xliff:g> vēlas rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Rediģēt"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Zvaniem un paziņojumiem tiks aktivizēta vibrācija."</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nevar piekļūt tālruņa kamerai no jūsu ierīces (<xliff:g id="DEVICE">%1$s</xliff:g>)."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nevar piekļūt planšetdatora kamerai no jūsu ierīces (<xliff:g id="DEVICE">%1$s</xliff:g>)."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Straumēšanas laikā nevar piekļūt šim saturam. Mēģiniet tam piekļūt savā tālrunī."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Sistēmas noklusējums"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 442047d..b3d46db 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандардно, ID на повикувач не е скриен. Следен повик: не е скриен"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е предвидена."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Не може да го промените поставувањето за ID на повикувач."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилниот интернет се префрли на <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Ова може да го промените во секое време во „Поставки“"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Нема услуга за мобилен интернет"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Итните повици се недостапни"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Нема услуга за говорни повици"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Овозможува апликацијата да прави трајни делови од себеси во меморијата. Ова може да ја ограничи расположливата меморија на други апликации што го забавува телефонот."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"извршување услуга во преден план"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Дозволува апликацијата да ги користи услугите во преден план."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"измери простор за складирање на апликацијата"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Дозволува апликацијата да ги обнови кодот, податоците и величините на кеш."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"менува системски поставки"</string>
@@ -449,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Апликацијава може да снима аудио со микрофонот додека се користи апликацијата."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"снима аудио во заднината"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Апликацијава може да снима аудио со микрофонот во секое време."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"откривај снимања на екранот од прозорци на апликацијата"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Апликацијава ќе биде известена кога ќе се направи слика од екранот додека се користи апликацијата."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"испраќање наредби до SIM-картичката"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Овозможува апликацијата да испраќа наредби до SIM картичката. Ова е многу опасно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавајте ја физичката активност"</string>
@@ -700,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Дозволува апликацијата да ги чита видеодатотеките од споделениот капацитет."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"да чита датотеки со слики од споделениот капацитет"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Дозволува апликацијата да ги чита датотеките со слики од споделениот капацитет."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"да чита датотеки со слики и видеа избрани од корисникот од споделениот простор."</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Дозволува апликацијата да чита датотеки со слики и видеа што ќе ги изберете од вашиот споделен простор."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ги менува или брише содржините на заедничкото место за складирање"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Дозволува апликацијата да ги пишува содржините на заедничкото место за складирање."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"остварува/прима повици преку SIP"</string>
@@ -2050,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ДЕИНСТАЛИРАЈ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"СЕПАК ОТВОРИ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Откриена е штетна апликација"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Да се дозволи <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> да пристапува до целата евиденција на уредот?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дозволи еднократен пристап"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволувај"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот.\n\nДознајте повеќе на g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Не прикажувај повторно"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> сака да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Измени"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Повиците и известувањата ќе вибрираат"</string>
@@ -2296,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не може да се пристапи до камерата на вашиот телефон од <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не може да се пристапи до камерата на вашиот таблет од <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"До ова не може да се пристапи при стриминг. Наместо тоа, пробајте на вашиот телефон."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Стандардно за системот"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТИЧКА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 5b59e66..efc475c 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"മെമ്മറിയിൽ അപ്ലിക്കേഷനുകളുടെ ഭാഗങ്ങൾ നിലനിർത്താൻ സ്വയം അനുവദിക്കുന്നു. ഇത് ഫോണിനെ മന്ദഗതിയിലാക്കുന്ന വിധത്തിൽ മറ്റ് അപ്ലിക്കേഷനുകൾക്ക് ലഭ്യമായ മെമ്മറി പരിമിതപ്പെടുത്താനിടയുണ്ട്."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"മുൻവശത്തുള്ള സേവനം റൺ ചെയ്യുക"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"മുൻവശത്തുള്ള സേവനങ്ങൾ ഉപയോഗിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"അപ്ലിക്കേഷൻ സംഭരണയിടം അളക്കുക"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"അപ്ലിക്കേഷന്റെ കോഡ്, ഡാറ്റ, കാഷെ വലുപ്പങ്ങൾ എന്നിവ വീണ്ടെടുക്കുന്നതിന് അതിനെ അനുവദിക്കുക"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"സിസ്റ്റം ക്രമീകരണങ്ങൾ പരിഷ്ക്കരിക്കുക"</string>
@@ -447,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ആപ്പ് ഉപയോഗത്തിലായിരിക്കുമ്പോൾ മൈക്രോഫോൺ ഉപയോഗിച്ച് ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ ഈ ആപ്പിന് കഴിയും."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"പശ്ചാത്തലത്തിൽ ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ഈ ആപ്പിന് ഏത് സമയത്തും മൈക്രോഫോൺ ഉപയോഗിച്ച് ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ കഴിയും."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM-ലേക്ക് കമാൻഡുകൾ അയയ്ക്കുക"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"സിമ്മിലേക്ക് കമാൻഡുകൾ അയയ്ക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഇത് വളരെ അപകടകരമാണ്."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ശാരീരിക പ്രവർത്തനം തിരിച്ചറിയുക"</string>
@@ -698,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"നിങ്ങളുടെ പങ്കിട്ട സ്റ്റോറേജിൽ നിന്നുള്ള വീഡിയോ ഫയലുകൾ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"പങ്കിട്ട സ്റ്റോറേജിൽ നിന്നുള്ള ചിത്ര ഫയലുകൾ വായിക്കുക"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"നിങ്ങളുടെ പങ്കിട്ട സ്റ്റോറേജിൽ നിന്നുള്ള ചിത്ര ഫയലുകൾ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"പങ്കിട്ട സ്റ്റോറേജിൽ നിന്ന് ഉപയോക്താവ് തിരഞ്ഞെടുത്ത ചിത്രത്തിന്റെയും വീഡിയോയുടെയും ഫയലുകൾ വായിക്കുക"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"പങ്കിട്ട സ്റ്റോറേജിൽ നിന്ന് നിങ്ങൾ തിരഞ്ഞെടുത്ത ചിത്രത്തിന്റെയും വീഡിയോയുടെയും ഫയലുകൾ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"നിങ്ങൾ പങ്കിടുന്ന സ്റ്റോറേജിലെ ഉള്ളടക്കങ്ങൾ പരിഷ്ക്കരിക്കുക അല്ലെങ്കിൽ ഇല്ലാതാക്കുക"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"നിങ്ങൾ പങ്കിടുന്ന സ്റ്റോറേജിലെ ഉള്ളടക്കങ്ങൾ എഴുതാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP കോളുകൾ വിളിക്കുക/സ്വീകരിക്കുക"</string>
@@ -2048,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"അൺഇൻസ്റ്റാള് ചെയ്യുക"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"എന്തായാലും തുറക്കുക"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ദോഷകരമായ ആപ്പ് കണ്ടെത്തി"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാൻ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ഒറ്റത്തവണ ആക്സസ് അനുവദിക്കുക"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"അനുവദിക്കരുത്"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ഉപകരണ ലോഗുകൾ നിങ്ങളുടെ ഉപകരണത്തിൽ എന്തൊക്കെയാണ് സംഭവിക്കുന്നതെന്ന് റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്സസ് ചെയ്യാനായേക്കും."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ഉപകരണ ലോഗുകൾ നിങ്ങളുടെ ഉപകരണത്തിൽ എന്തൊക്കെയാണ് സംഭവിക്കുന്നതെന്ന് റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്സസ് ചെയ്യാനായേക്കും.\n\ng.co/android/devicelogs എന്നതിൽ കൂടുതലറിയുക."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"വീണ്ടും കാണിക്കരുത്"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g> താൽപ്പര്യപ്പെടുന്നു"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"എഡിറ്റ് ചെയ്യുക"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"കോളുകളും അറിയിപ്പുകളും വൈബ്രേറ്റ് ചെയ്യും"</string>
@@ -2294,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഫോണിന്റെ ക്യാമറ ആക്സസ് ചെയ്യാനാകില്ല"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> എന്നതിൽ നിന്ന് ടാബ്ലെറ്റിന്റെ ക്യാമറ ആക്സസ് ചെയ്യാനാകില്ല"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"സ്ട്രീം ചെയ്യുമ്പോൾ ഇത് ആക്സസ് ചെയ്യാനാകില്ല. പകരം നിങ്ങളുടെ ഫോണിൽ ശ്രമിച്ച് നോക്കൂ."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"സിസ്റ്റം ഡിഫോൾട്ട്"</string>
<string name="default_card_name" msgid="9198284935962911468">"കാർഡ് <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 03deebb..114d562 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдсан"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Үйлчилгээ провишн хийгдээгүй ."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Та дуудлага хийгчийн ID тохиргоог солиж чадахгүй."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Өгөгдлийг <xliff:g id="CARRIERDISPLAY">%s</xliff:g> руу шилжүүлсэн"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Та үүнийг Тохиргооноос хэдийд ч өөрчлөх боломжтой."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобайл дата үйлчилгээ алга"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Яаралтай дуудлага боломжтой"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дуу хоолойны үйлчилгээ алга"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Апп нь өөрийн хэсгийг санах ойд байнга байлгах боломжтой. Энэ нь бусад апп-уудын ашиглах санах ойг хязгаарлан утсыг удаашруулах болно."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"интерактив (foreground) үйлчилгээг ажиллуулах"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Аппад интерактив (foreground) үйлчилгээг ашиглахыг зөвшөөрнө үү."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"апп сангийн хэмжээг хэмжих"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Апп нь өөрийн код, дата болон кеш хэмжээг унших боломжтой"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"систем тохиргоог өөрчлөх"</string>
@@ -449,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Энэ аппыг ашиглаж байх үед энэ нь микрофон ашиглан аудио бичих боломжтой."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ард видео бичих"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Энэ апп ямар ч үед микрофон ашиглан аудио бичих боломжтой."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"аппын цонхны дэлгэцийн зургийг илрүүлэх"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Энэ аппыг ашиглаж байх үеэр дэлгэцийн агшин авбал мэдэгдэл хүлээн авах болно."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM картад тушаал илгээх"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Апп-д SIM рүү комманд илгээхийг зөвшөөрнө. Энэ маш аюултай."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"биеийн дасгал хөдөлгөөн таних"</string>
@@ -700,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Аппад таны дундын хадгалах сангаас видео файлыг унших боломжийг олгодог."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"дундын хадгалах сангаас зургийн файл унших"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Аппад таны дундын хадгалах сангаас зургийн файлыг унших зөвшөөрөл олгодог."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"дундын хадгалах сангаас хэрэглэгчийн сонгосон зураг болон видео файлуудыг унших"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Аппад таны дундын хадгалах сангаас сонгосон зураг болон видео файлуудыг унших боломж олгодог."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"дундын хадгалах сангийнхаа контентыг өөрчлөх эсвэл устгах"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Аппад таны дундын хадгалах сангийн контентыг бичихийг зөвшөөрдөг."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP дуудлага хийх/хүлээн авах"</string>
@@ -2050,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"УСТГАХ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ЯМАР Ч ТОХИОЛДОЛД НЭЭХ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Аюултай апп олдсон"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-д төхөөрөмжийн бүх логт хандахыг зөвшөөрөх үү?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Нэг удаагийн хандалтыг зөвшөөрнө үү"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Бүү зөвшөөр"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Tаны төхөөрөмж үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Таны төхөөрөмжийн үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй.\n\ng.co/android/devicelogs -с нэмэлт мэдээлэл аваарай."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Дахиж бүү харуул"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг (slices) харуулах хүсэлтэй байна"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Засах"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Дуудлага болон мэдэгдэл чичирнэ"</string>
@@ -2296,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Таны <xliff:g id="DEVICE">%1$s</xliff:g>-с утасны камерт хандах боломжгүй"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Таны <xliff:g id="DEVICE">%1$s</xliff:g>-с таблетын камерт хандах боломжгүй"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Стримингийн үед үүнд хандах боломжгүй. Оронд нь утас дээрээ туршиж үзнэ үү."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Системийн өгөгдмөл"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 1d25e67..216d960 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आयडी डीफॉल्ट रूपात प्रतिबंधित नाही वर सेट असतो. पुढील कॉल: प्रतिबंधित नाही"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवेची तरतूद केलेली नाही."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"तुम्ही कॉलर आयडी सेटिंग बदलू शकत नाही."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"डेटा <xliff:g id="CARRIERDISPLAY">%s</xliff:g> वर स्विच केला"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"तुम्ही हे सेटिंग्जमध्ये कधीही बदलू शकता"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"मोबाइल डेटा सेवा नाही"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आणीबाणी कॉलिंग अनुपलब्ध आहे"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"व्हॉइस सेवा नाही"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"अॅप ला मेमरीमध्ये कायम असलेले त्याचे स्वतःचे भाग बनविण्यास अनुमती देते. हे फोन धीमा करून अन्य अॅप्सवर उपलब्ध असलेल्या मेमरीवर मर्यादा घालू शकते."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"पृष्ठभाग सेवा रन करा"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"अॅपला पृष्ठभाग सेवा वापरण्याची अनुमती देते."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"अॅप संचयन स्थान मोजा"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"अॅप ला त्याचा कोड, डेटा आणि कॅशे आकार पुनर्प्राप्त करण्यासाठी अनुमती देते"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"सिस्टीम सेटिंग्ज सुधारित करा"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ॲप वापरात असताना, हे ॲप मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करू शकते."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"बॅकग्राउंडमध्ये ऑडिओ रेकॉर्ड करा"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"हे ॲप मायक्रोफोन वापरून ऑडिओ कधीही रेकॉर्ड करू शकते."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"सिम वर कमांड पाठवा"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"अॅप ला सिम वर कमांड पाठविण्याची अनुमती देते. हे खूप धोकादायक असते."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक ॲक्टिव्हिटी ओळखा"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ॲपला तुमच्या शेअर केलेल्या स्टोरेजमधून व्हिडिओ फाइल वाचण्याची अनुमती देते."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"शेअर केलेल्या स्टोरेजमधून इमेज फाइल वाचा"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ॲपला तुमच्या शेअर केलेल्या स्टोरेजमधून इमेज फाइल वाचण्याची अनुमती देते."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"शेअर केलेल्या स्टोरेजमधून वापरकर्त्याने निवडलेल्या इमेज आणि व्हिडिओ फाइल वाचा"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"तुमच्या शेअर केलेले स्टोरेजमधून तुम्ही निवडलेल्या इमेज आणि व्हिडिओ फाइल वाचण्याची अॅपना अनुमती देते."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"तुमच्या शेअर केलेल्या स्टोरेजच्या आशयांमध्ये सुधारणा करा किंवा हटवा"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ॲपला तुमच्या शेअर केलेल्या स्टोरेजचे आशय लिहिण्याची अनमती देते."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP कॉल करा/मिळवा"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"अनइंस्टॉल करा"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"तरीही उघडा"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"हानिकारक अॅप आढळला"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ला सर्व डिव्हाइस लॉग अॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक वेळ अॅक्सेसची अनुमती द्या"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमती देऊ नका"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्याकरिता ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अॅक्सेस करू शकतो."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्याकरिता ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अॅक्सेस करू शकतो.\n\ng.co/android/devicelogs येथे अधिक जाणून घ्या."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"पुन्हा दाखवू नका"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवायचे आहेत"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"संपादित करा"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कॉल आणि सूचनांवर व्हायब्रेट होईल"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वरून फोनचा कॅमेरा अॅक्सेस करू शकत नाही"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वरून टॅबलेटचा कॅमेरा अॅक्सेस करू शकत नाही"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रीम करताना हे अॅक्सेस केले जाऊ शकत नाही. त्याऐवजी तुमच्या फोनवर अॅक्सेस करून पहा."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"सिस्टीम डीफॉल्ट"</string>
<string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 72d3f2f..112190b 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pemanggil secara lalainya ditetapkan kepada tidak dihadkan. Panggilan seterusnya: Tidak terhad"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Perkhidmatan yang tidak diuntukkan."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak boleh mengubah tetapan ID pemanggil."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Data ditukar kepada <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Anda boleh menukar pilihan ini pada bila-bila masa dalam Tetapan"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Tiada perkhidmatan data mudah alih"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Panggilan kecemasan tidak tersedia"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tiada perkhidmatan suara"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Membenarkan apl untuk membuat sebahagian dari dirinya berterusan dalam memori. Ini boleh mengehadkan memori yang tersedia kepada apl lain dan menjadikan telefon perlahan."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"jalankan perkhidmatan latar depan"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Membenarkan apl menggunakan perkhidmatan latar depan."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ukur ruang storan apl"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Membenarkan apl mendapatkan semula kodnya, datanya dan saiz cachenya"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ubah suai tetapan sistem"</string>
@@ -449,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikasi ini boleh merakam audio menggunakan mikrofon semasa apl sedang digunakan."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rakam audio di latar"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Apl ini boleh merakam audio menggunakan mikrofon pada bila-bila masa."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"mengesan tangkapan skrin tetingkap apl"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Apl ini akan dimaklumkan apabila tangkapan skrin diambil semasa apl sedang digunakan."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"hantar perintah ke SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Membenarkan apl menghantar arahan kepada SIM. Ini amat berbahaya."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"camkan aktiviti fizikal"</string>
@@ -700,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Membenarkan apl membaca fail video daripada storan kongsi anda."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"baca fail imej daripada storan kongsi"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Membenarkan apl membaca fail imej daripada storan kongsi anda."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"baca fail imej dan video yang dipilih oleh pengguna daripada storan kongsi"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Membenarkan apl membaca fail imej dan video yang anda pilih daripada storan kongsi anda."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"mengubah suai atau memadamkan kandungan storan kongsi anda"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Membenarkan apl menulis kandungan storan kongsi anda."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"buat/terima panggilan SIP"</string>
@@ -2050,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"NYAHPASANG"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"BUKA JUGA"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Apl berbahaya dikesan"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Benarkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log peranti?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Benarkan akses satu kali"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Jangan benarkan"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl dapat menggunakan log ini untuk menemukan dan membetulkan isu.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi benarkan apl yang anda percaya sahaja untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl boleh menggunakan log ini untuk menemukan dan membetulkan masalah.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi hanya benarkan apl yang anda percaya untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl ini masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda.\n\nKetahui lebih lanjut di g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Jangan tunjuk lagi"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> mahu menunjukkan <xliff:g id="APP_2">%2$s</xliff:g> hirisan"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Panggilan dan pemberitahuan akan bergetar"</string>
@@ -2296,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Tidak dapat mengakses kamera telefon daripada <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Tidak dapat mengakses kamera tablet daripada <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Kandungan ini tidak boleh diakses semasa penstriman. Cuba pada telefon anda."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Lalai sistem"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 5ff033d..8e9b9ad 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ဝန်ဆောင်မှုအား ကန့်သတ်မထားပါ"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"သင်သည် ခေါ်ဆိုသူ ID ဆက်တင်ကို မပြောင်းလဲနိုင်ပါ။"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"ဒေတာကို <xliff:g id="CARRIERDISPLAY">%s</xliff:g> သို့ ပြောင်းထားသည်"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"၎င်းကို ဆက်တင်များတွင် အချိန်မရွေး ပြောင်းနိုင်သည်"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"မိုဘိုင်း ဒေတာဝန်ဆောင်မှု မရှိပါ"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"အရေးပေါ်ခေါ်ဆိုမှု မရနိုင်ပါ"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ဖုန်းဝန်ဆောင်မှု မရှိပါ"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"အပလီကေးရှင်းအား မှတ်ဉာဏ်ထဲတွင် ရေရှည်သိမ်းဆည်ထားရန် ခွင့်ပြုပါ။ ဒီခွင့်ပြုချက်ကြောင့် တခြားအပလီကေးရှင်းအများအတွက် မှတ်ဉာဏ်ရရှိမှု နည်းသွားနိုင်ပြီး ဖုန်းလည်း နှေးသွားနိုင်ပါသည်။"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"မျက်နှာစာ ဝန်ဆောင်မှုကို ဖွင့်ခြင်း"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"မျက်နှာစာဝန်ဆောင်မှုများကို အက်ပ်အား အသုံးပြုခွင့်ပေးသည်။"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"အက်ပ်သိုလှောင်မှု နေရာကို တိုင်းထွာခြင်း"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"အက်ပ်အား ၎င်း၏ ကုဒ်၊ ဒေတာ၊ နှင့် ကက်ရှ ဆိုက်များကို ရယူခွင့် ပြုသည်။"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"စနစ်အပြင်အဆင်အား မွမ်းမံခြင်း"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ဤအက်ပ်ကို အသုံးပြုနေစဉ် ၎င်းက မိုက်ခရိုဖုန်းကို အသုံးပြု၍ အသံဖမ်းနိုင်သည်။"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"နောက်ခံတွင် အသံဖမ်းပါ"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ဤအက်ပ်သည် မိုက်ခရိုဖုန်းကို အသုံးပြု၍ အချိန်မရွေး အသံဖမ်းနိုင်သည်။"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM ထံသို့ ညွှန်ကြားချက်များကို ပို့ပါ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"အက်ပ်အား ဆင်းမ်ကဒ်ဆီသို့ အမိန့်များ ပေးပို့ခွင့် ပြုခြင်း။ ဤခွင့်ပြုမှုမှာ အန္တရာယ်အလွန် ရှိပါသည်။"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ကိုယ်ခန္ဓာလှုပ်ရှားမှုကို မှတ်သားပါ"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"သင်၏မျှဝေထားသော သိုလှောင်ခန်းမှ ဗီဒီယိုဖိုင်များဖတ်ရန် အက်ပ်ကိုခွင့်ပြုသည်။"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"မျှဝေထားသည့် သိုလှောင်ခန်းမှ ပုံပါဝင်သောဖိုင်များဖတ်ရန်"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"သင်၏မျှဝေထားသည့် သိုလှောင်ခန်းမှ ပုံပါဝင်သောဖိုင်များဖတ်ရန် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"မျှသုံးသိုလှောင်ခန်းမှ အသုံးပြုသူ ရွေးချယ်ထားသော ပုံနှင့် ဗီဒီယိုဖိုင်များ ဖတ်ခြင်း"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"မျှသုံးသိုလှောင်ခန်းမှ သင်ရွေးချယ်သည့် ပုံနှင့် ဗီဒီယိုဖိုင်များအား အက်ပ်ကို ဖတ်ခွင့်ပြုသည်။"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"မျှဝေသိုလှောင်ခန်းမှ အရာများ ပြုပြင်/ဖျက်ခြင်း"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"မျှဝေသိုလှောင်ခန်းမှ အရာများ ရေးခွင့်ပြုသည်။"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP ခေါ်ဆိုမှုများ ခေါ်ရန်/လက်ခံရန်"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ဖြုတ်ရန်"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ဘာဖြစ်ဖြစ် ဖွင့်ရန်"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"အန္တရာယ်ရှိသော အက်ပ်ကို တွေ့ရှိထားသည်"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုမလား။"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"တစ်ခါသုံး ဝင်ခွင့်ပေးရန်"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ခွင့်မပြုပါ"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် စက်မှတ်တမ်းအားလုံးကို ယုံကြည်ရသည့် အက်ပ်များကိုသာ သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ၎င်း၏ကိုယ်ပိုင်မှတ်တမ်းကို သုံးနိုင်ဆဲဖြစ်သည်။ သင့်စက်ရှိ အချို့မှတ်တမ်းများ (သို့) အချက်အလက်များကို သင့်စက်ထုတ်လုပ်သူက သုံးနိုင်ပါသေးသည်။"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် ယုံကြည်ရသည့် အက်ပ်များကိုသာ စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ကိုယ်ပိုင်မှတ်တမ်းများ သုံးနိုင်သေးသည်။ သင့်စက်ရှိ မှတ်တမ်း (သို့) အချက်အလက်အချို့ကို စက်ထုတ်လုပ်သူက သုံးနိုင်သေးသည်။\n\ng.co/android/devicelogs တွင် ပိုမိုလေ့လာပါ။"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"နောက်ထပ်မပြပါနှင့်"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> သည် <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များကို ပြသလိုသည်"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"တည်းဖြတ်ရန်"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ခေါ်ဆိုမှုများနှင့် အကြောင်းကြားချက်များ တုန်ခါပါမည်"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"သင်၏ <xliff:g id="DEVICE">%1$s</xliff:g> မှ ဖုန်းကင်မရာကို သုံး၍မရပါ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"သင်၏ <xliff:g id="DEVICE">%1$s</xliff:g> မှ တက်ဘလက်ကင်မရာကို သုံး၍မရပါ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"တိုက်ရိုက်လွှင့်နေစဉ် ၎င်းကို မသုံးနိုင်ပါ။ ၎င်းအစား ဖုန်းတွင် စမ်းကြည့်ပါ။"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"စနစ်မူရင်း"</string>
<string name="default_card_name" msgid="9198284935962911468">"ကတ် <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index bb4fbe4..dc8be27 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummervisning er ikke begrenset som standard. Neste anrop: Ikke begrenset"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"SIM-kortet er ikke tilrettelagt for tjenesten."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke endre innstillingen for anrops-ID."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Byttet data til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Du kan endre dette når som helst i innstillingene"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjeneste"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Nødanrop er utilgjengelig"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ingen taletjeneste"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Lar appen gjøre deler av seg selv vedvarende i minnet. Dette kan begrense minnet for andre apper og gjøre telefonen treg."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"kjøre tjenesten i forgrunnen"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Lar appen bruke tjenester i forgrunnen."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"måle lagringsplass for apper"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Lar appen hente ut koden, dataene og bufferstørrelsene til appen"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"endre systeminnstillingene"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Denne appen kan ta opp lyd med mikrofonen mens den er i bruk."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ta opp lyd i bakgrunnen"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Denne appen kan når som helst ta opp lyd med mikrofonen."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"sende kommandoer til SIM-kortet"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Lar appen sende kommandoer til SIM-kortet. Dette er veldig farlig."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"gjenkjenn fysisk aktivitet"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Lar appen lese videofiler fra den delte lagringsplassen din."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"lese bildefiler fra delt lagringsplass"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Lar appen lese bildefiler fra den delte lagringsplassen din."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lese brukervalgte bilde- og videofiler fra den delte lagringen"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"lar appen lese bilde- og videofiler du velger fra den delte lagringen din"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"endre eller slette innholdet i den delte lagringen din"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Lar appen skrive innholdet i den delte lagringen din."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"foreta/motta SIP-anrop"</string>
@@ -1156,7 +1208,7 @@
<string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Bytt inndatametode"</string>
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Lite ledig lagringsplass"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Enkelte systemfunksjoner fungerer muligens ikke slik de skal"</string>
- <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontrollér at du har 250 MB ledig plass, og start på nytt."</string>
+ <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontroller at du har 250 MB ledig plass, og start på nytt."</string>
<string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> kjører"</string>
<string name="app_running_notification_text" msgid="5120815883400228566">"Trykk for å få mer informasjon eller for å stoppe appen."</string>
<string name="ok" msgid="2646370155170753815">"OK"</string>
@@ -1689,7 +1741,7 @@
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"AV"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"Vil du gi <xliff:g id="SERVICE">%1$s</xliff:g> full kontroll over enheten din?"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"Full kontroll er passende for apper som hjelper deg med tilgjengelighetsbehov, men ikke for de fleste apper."</string>
- <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og kontrollér skjermen"</string>
+ <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og kontroller skjermen"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Den kan lese alt innhold på skjermen og vise innhold over andre apper."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Se og utfør handlinger"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore kommunikasjonen din med en app eller maskinvaresensor og kommunisere med apper på dine vegne."</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"AVINSTALLER"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ÅPNE LIKEVEL"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"En skadelig app ble oppdaget"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vil du gi <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tilgang til alle enhetslogger?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Gi éngangstilgang"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ikke tillat"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Enhetslogger registrerer det som skjer på enheten din. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten din."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhetslogger registrerer det som skjer på enheten. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten.\n\nFinn ut mer på g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ikke vis igjen"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vil vise <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Endre"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Anrop og varsler vibrerer"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Det er ikke mulig å få tilgang til telefonkameraet fra <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Det er ikke mulig å få tilgang til kameraet på nettbrettet fra <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Dette er ikke tilgjengelig under strømming. Prøv på telefonen i stedet."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Systemstandard"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index ea96df0..b031aa8 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवाको व्यवस्था छैन।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> को डेटा प्रयोग गर्न थालिएको छ"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"तपाईं जुनसुकै बेला सेटिङमा गई यो कुरा बदल्न सक्नुहुन्छ"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"कुनै पनि मोबाइल डेटा सेवा उपलब्ध छैन"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आपत्कालीन कल सेवा उपलब्ध छैन"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"कुनै पनि भ्वाइस सेवा उपलब्ध छैन"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"एपलाई मेमोरीमा आफैंको निरन्तरको अंश बनाउन अनुमति दिन्छ। यसले फोनलाई ढिला बनाएर अन्य एपहरूमा मेमोरी SIMित गर्न सक्दछन्।"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"अग्रभूमिको सेवा सञ्चालन गर्नुहोस्"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"एपलाई अग्रभूमिका सेवाहरू प्रयोग गर्ने अनुमति दिन्छ।"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"एप भण्डारण ठाउँको मापन गर्नुहोस्"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"एपलाई यसको कोड, डेटा, र क्यास आकारहरू पुनःप्राप्त गर्न अनुमति दिन्छ।"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"प्रणाली सेटिङहरू परिमार्जन गर्नुहोस्"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"यो एप प्रयोग भइरहेका बेला यसले माइक्रोफोन प्रयोग गरेर अडियो रेकर्ड गर्न सक्छ।"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ब्याकग्राउन्डमा अडियो रेकर्ड गर्नुहोस्"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"यो एपले जुनसुकै बेला माइक्रोफोन प्रयोग गरी अडियो रेकर्ड गर्न सक्छ।"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM मा आदेशहरू पठाउन दिनुहोस्"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM लाई आदेश पठाउन एपलाई अनुमति दिन्छ। यो निकै खतरनाक हुन्छ।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक गतिविधि पहिचान गर्नुहोस्"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"एपलाई तपाईंको साझा भण्डारणमा भएका भिडियो फाइलहरू पढ्ने अनुमति दिन्छ।"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"साझा भण्डारणमा भएका फोटो फाइलहरू पढ्ने"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"एपलाई तपाईंको साझा भण्डारणमा भएका फोटो फाइलहरू पढ्ने अनुमति दिन्छ।"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"प्रयोगकर्ताले साझा भण्डारणबाट चयन गरेका फोटो र भिडियो फाइलहरू पढ्ने"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"एपलाई तपाईंले आफ्नो साझा भण्डारणबाट चयन गर्नुभएका फोटो र भिडियो फाइलहरू पढ्ने अनुमति दिन्छ।"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"तपाईंको आदान प्रदान गरिएको भण्डारणको विषयवस्तुहरूलाई परिमार्जन गर्नहोस् वा मेटाउनुहोस्"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"एपलाई तपाईंको आदान प्रदान गरिएको भण्डारणको सामग्री लेख्न अनुमति दिन्छ।"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP कलहरू प्राप्त/बनाउन"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"स्थापना रद्द गर्नु…"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"जे भए पनि खोल्नुहोस्"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"हानिकारक एप भेटियो"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> लाई डिभाइसका सबै लग हेर्ने अनुमति दिने हो?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक पटक प्रयोग गर्ने अनुमति दिनुहोस्"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति नदिनुहोस्"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।\n\nतपाईं यस सम्बन्धमा थप जान्न चाहनुहुन्छ भने g.co/android/devicelogs मा जानुहोस्।"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फेरि नदेखाइयोस्"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ले <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन चाहन्छ"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"सम्पादन गर्नुहोस्"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कल तथा सूचनाहरू आउँदा कम्पन हुने छ"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मार्फत फोनको क्यामेरा प्रयोग गर्न मिल्दैन"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मार्फत ट्याब्लेटको क्यामेरा प्रयोग गर्न मिल्दैन"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रिम गरिरहेका बेला यो सामग्री हेर्न तथा प्रयोग गर्न मिल्दैन। बरु आफ्नो फोनमार्फत सो सामग्री हेर्ने तथा प्रयोग गर्ने प्रयास गर्नुहोस्।"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"सिस्टम डिफल्ट"</string>
<string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 24877dd..566bbb3 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID standaard ingesteld op \'onbeperkt\'. Volgend gesprek: onbeperkt."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Service niet voorzien."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"U kunt de instelling voor de beller-ID niet wijzigen."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiele data overgeschakeld naar <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Je kunt dit altijd wijzigen via Instellingen"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Geen service voor mobiele data"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Noodoproepen niet beschikbaar"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Geen belservice"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Hiermee kan de app gedeelten van zichzelf persistent maken in het geheugen. Dit kan de hoeveelheid geheugen beperken die beschikbaar is voor andere apps, waardoor de telefoon trager kan worden."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"service op de voorgrond uitvoeren"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Hiermee kan de app gebruikmaken van services op de voorgrond."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"opslagruimte van app meten"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Hiermee kan de app de bijbehorende code, gegevens en cachegrootten ophalen."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"systeeminstellingen aanpassen"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Deze app kan audio opnemen met de microfoon als de app wordt gebruikt."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"audio opnemen op de achtergrond"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Deze app kan altijd audio opnemen met de microfoon."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"opdrachten verzenden naar de simkaart"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Hiermee kan de app opdrachten verzenden naar de simkaart. Dit is erg gevaarlijk."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"fysieke activiteit herkennen"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Hiermee kan de app videobestanden in je gedeelde opslag lezen."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"afbeeldingsbestanden in gedeelde opslag lezen"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Hiermee kan de app afbeeldingsbestanden in je gedeelde opslag lezen."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"door de gebruiker geselecteerde afbeeldings- en videobestanden in de gedeelde opslag lezen"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Hiermee kan de app afbeeldings- en videobestanden lezen die je selecteert in je gedeelde opslag."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"de content van je gedeelde opslag aanpassen of verwijderen"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Hiermee kan de app de content van je gedeelde opslag schrijven."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"Bellen of gebeld worden via SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"VERWIJDEREN"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TOCH OPENEN"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Schadelijke app gevonden"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang geven tot alle apparaatlogboeken?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eenmalige toegang toestaan"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Niet toestaan"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat.\n\nGa voor meer informatie naar g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Niet opnieuw tonen"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wil segmenten van <xliff:g id="APP_2">%2$s</xliff:g> tonen"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Bewerken"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Trillen bij gesprekken en meldingen"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kan geen toegang tot de camera van de telefoon krijgen vanaf je <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kan geen toegang tot de camera van de tablet krijgen vanaf je <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Je hebt hier geen toegang toe tijdens streaming. Probeer het in plaats daarvan op je telefoon."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Systeemstandaard"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 456fc83..26880c5 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ସେବାର ସୁବିଧା ନାହିଁ।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ଆପଣ କଲର୍ ID ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>କୁ ଡାଟା ସ୍ୱିଚ କରାଯାଇଛି"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"ଆପଣ ଯେ କୌଣସି ସମୟରେ ସେଟିଂସରେ ଏହାକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"କୌଣସି ମୋବାଇଲ୍ ଡାଟା ସେବା ନାହିଁ"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ଜରୁରୀକାଳୀନ କଲ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"କୌଣସି ଭଏସ୍ ସେବା ନାହିଁ"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ଆପ୍ଟି ନିଜକୁ ମେମୋରୀରେ ଭାଗ କରିବାକୁ ଦେଇଥାଏ। ଏହାଦ୍ୱାରା ଅନ୍ୟ ଆପ୍ଗୁଡ଼ିକ ପାଇଁ ମେମୋରୀ ଉପଲବ୍ଧକୁ କମ୍ କରିବା ସହ ଫୋନ୍ଟିକୁ ମନ୍ଥର କରିବ।"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ଫୋର୍ଗ୍ରାଉଣ୍ଡ ସେବାକୁ ଚଲାନ୍ତୁ"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ଫୋର୍ଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ଆପ୍ ଷ୍ଟୋରେଜ୍ ସ୍ଥାନର ମାପ କରନ୍ତୁ"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ଆପ୍ର କୋଡ୍, ଡାଟା ଓ କ୍ୟାଶ୍ ଆକାର ହାସଲ କରିବା ପାଇଁ ଏହାକୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ସିଷ୍ଟମ୍ ସେଟିଂସ ବଦଳାନ୍ତୁ"</string>
@@ -449,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ଆପକୁ ବ୍ୟବହାର କରାଯାଉଥିବା ସମୟରେ ଏହା ମାଇକ୍ରୋଫୋନକୁ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ପୃଷ୍ଠପଟରେ ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ଏହି ଆପ୍ ଯେ କୌଣସି ସମୟରେ ମାଇକ୍ରୋଫୋନକୁ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ଆପ ୱିଣ୍ଡୋର ସ୍କ୍ରିନ କେପଚରଗୁଡ଼ିକୁ ଚିହ୍ନଟ କରନ୍ତୁ"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ଆପଟି ବ୍ୟବହାରରେ ଥିବା ସମୟରେ ଏକ ସ୍କ୍ରିନସଟ ନିଆଗଲେ ଏହି ଆପକୁ ସୂଚିତ କରାଯିବ।"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIMକୁ କମାଣ୍ଡ ପଠାନ୍ତୁ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMକୁ କମାଣ୍ଡ ପଠାଇବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହା ବହୁତ ବିପଦପୂର୍ଣ୍ଣ।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ଶାରୀରିକ ଗତିବିଧି ଚିହ୍ନଟକରେ"</string>
@@ -700,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଭିଡିଓ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଇମେଜ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ନ୍ତୁ"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଇମେଜ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ୟୁଜରଙ୍କ ଦ୍ୱାରା ଚୟନିତ ଇମେଜ ଏବଂ ଭିଡିଓ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ଆପଣ ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଚୟନ କରିଥିବା ଇମେଜ ଏବଂ ଭିଡିଓ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ଆପଣଙ୍କତ ସେୟାର୍ ହୋଇଥିବା ଷ୍ଟୋରେଜ୍ର ବିଷୟବସ୍ତୁ ସଂଶୋଧନ କିମ୍ବା ଡିଲିଟ୍ କରନ୍ତୁ"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ଆପଣଙ୍କର ସେୟାର୍ ହୋଇଥିବା ଷ୍ଟୋରେଜ୍ର ବିଷୟବସ୍ତୁ ଲେଖିବାକୁ ଅନୁମତି କରିଥାଏ।"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP କଲ୍ କରନ୍ତୁ ଏବଂ ଗ୍ରହଣ କରନ୍ତୁ"</string>
@@ -2050,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"କୌଣସିମତେ ଖୋଲନ୍ତୁ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ହାନିକାରକ ଆପ୍ ଚିହ୍ନଟ ହୋଇଛି"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ଗୋଟିଏ-ଥର ଆକ୍ସେସ ପାଇଁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ବି ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ବି ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।\n\ng.co/android/devicelogsରେ ଅଧିକ ଜାଣନ୍ତୁ।"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>, <xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ ଚାହେଁ"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ଏଡିଟ କରନ୍ତୁ"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"କଲ୍ ଓ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଭାଇବ୍ରେଟ୍ ହେବ"</string>
@@ -2296,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରୁ ଫୋନର କ୍ୟାମେରାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରୁ ଟାବଲେଟର କ୍ୟାମେରାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ଷ୍ଟ୍ରିମ କରିବା ସମୟରେ ଏହାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ ଫୋନରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"ସିଷ୍ଟମ ଡିଫଲ୍ଟ"</string>
<string name="default_card_name" msgid="9198284935962911468">"କାର୍ଡ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 79ab897..6727f74 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ਪ੍ਰਤਿਬੰਧਿਤ ਨਾ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ਸੇਵਾ ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ਤੁਸੀਂ ਕਾਲਰ ਆਈ.ਡੀ. ਸੈਟਿੰਗ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"ਡਾਟੇ ਨੂੰ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"ਤੁਸੀਂ ਕਿਸੇ ਵੇਲੇ ਵੀ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਇਸਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ਕੋਈ ਮੋਬਾਈਲ ਡਾਟਾ ਸੇਵਾ ਨਹੀਂ"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ਸੰਕਟਕਾਲੀਨ ਕਾਲਿੰਗ ਉਪਲਬਧ ਨਹੀਂ"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ਕੋਈ ਆਵਾਜ਼ੀ ਸੇਵਾ ਨਹੀਂ"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ਐਪ ਨੂੰ ਮੈਮਰੀ ਵਿੱਚ ਖੁਦ ਦੇ ਭਾਗਾਂ ਨੂੰ ਸਥਾਈ ਬਣਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਫ਼ੋਨ ਨੂੰ ਹੌਲੀ ਕਰਦੇ ਹੋਏ ਹੋਰਾਂ ਐਪਾਂ ਤੇ ਉਪਲਬਧ ਮੈਮਰੀ ਨੂੰ ਸੀਮਿਤ ਕਰ ਸਕਦਾ ਹੈ।"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ਫੋਰਗ੍ਰਾਉਂਡ ਸੇਵਾਵਾਂ ਚਲਾਓ"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ਐਪ ਨੂੰ ਫੋਰਗ੍ਰਾਉਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦਿਓ।"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ਐਪ ਸਟੋਰੇਜ ਜਗ੍ਹਾ ਮਾਪੋ"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ਐਪ ਨੂੰ ਇਸਦਾ ਕੋਡ, ਡਾਟਾ ਅਤੇ ਕੈਸ਼ੇ ਆਕਾਰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ਸਿਸਟਮ ਸੈਟਿੰਗਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰੋ"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ਇਹ ਐਪ ਵਰਤੋਂ ਵਿੱਚ ਹੋਣ ਵੇਲੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਵਰਤ ਕੇ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ਇਹ ਐਪ ਕਿਸੇ ਵੇਲੇ ਵੀ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਵਰਤ ਕੇ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜੋ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ਐਪ ਨੂੰ SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਬਹੁਤ ਘਾਤਕ ਹੈ।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ਸਰੀਰਕ ਸਰਗਰਮੀ ਨੂੰ ਪਛਾਣਨਾ"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ਐਪ ਨੂੰ ਤੁਹਾਡੀ ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਚਿੱਤਰ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹੋ"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ਐਪ ਨੂੰ ਤੁਹਾਡੀ ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਚਿੱਤਰ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਵਰਤੋਂਕਾਰ ਵੱਲੋਂ ਚੁਣੀਆਂ ਚਿੱਤਰ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ਇਹ ਐਪ ਨੂੰ ਉਨ੍ਹਾਂ ਚਿੱਤਰ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਨੂੰ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਆਪਣੀ ਸਾਂਝੀ ਸਟੋਰੇਜ ਤੋਂ ਚੁਣਦੇ ਹੋ।"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ਸਮੱਗਰੀਆਂ ਦਾ ਸੰਸ਼ੋਧਨ ਕਰੋ ਜਾਂ ਮਿਟਾਓ"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ਐਪ ਨੂੰ ਸਮੱਗਰੀਆਂ ਲਿਖਣ ਦਿੰਦੀ ਹੈ।"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP ਕਾਲਾਂ ਕਰੋ/ਪ੍ਰਾਪਤ ਕਰੋ"</string>
@@ -1288,7 +1340,7 @@
<string name="volume_icon_description_media" msgid="4997633254078171233">"ਮੀਡੀਆ ਦੀ ਅਵਾਜ਼"</string>
<string name="volume_icon_description_notification" msgid="579091344110747279">"ਸੂਚਨਾ ਵੌਲਿਊਮ"</string>
<string name="ringtone_default" msgid="9118299121288174597">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਰਿੰਗਟੋਨ"</string>
- <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ਪੂਰਵ-ਨਿਰਧਾਰਤ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="397111123930141876">"ਕੋਈ ਨਹੀਂ"</string>
<string name="ringtone_picker_title" msgid="667342618626068253">"ਰਿੰਗਟੋਨਾਂ"</string>
<string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"ਅਲਾਰਮ ਧੁਨੀਆਂ"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ਅਣਸਥਾਪਤ ਕਰੋ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ਫਿਰ ਵੀ ਖੋਲ੍ਹੋ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ਹਾਨੀਕਾਰਕ ਐਪ ਦਾ ਪਤਾ ਲੱਗਿਆ"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"ਕੀ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ਇੱਕ-ਵਾਰ ਲਈ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ਆਗਿਆ ਨਾ ਦਿਓ"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।\n\ng.co/android/devicelogs \'ਤੇ ਹੋਰ ਜਾਣੋ।"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ਦੀ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੀ ਇੱਛਾ ਹੈ"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ਸੰਪਾਦਨ ਕਰੋ"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ਕਾਲਾਂ ਅਤੇ ਸੂਚਨਾਵਾਂ ਦੀ ਥਰਥਰਾਹਟ ਹੋਵੇਗੀ"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> ਤੋਂ ਫ਼ੋਨ ਦੇ ਕੈਮਰੇ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> ਤੋਂ ਟੈਬਲੈੱਟ ਦੇ ਕੈਮਰੇ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ਸਟ੍ਰੀਮਿੰਗ ਦੌਰਾਨ ਇਸ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"ਸਿਸਟਮ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ਕਾਰਡ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 5e3a64a..0441a5f 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID rozmówcy ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Usługa nie jest świadczona."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nie możesz zmienić ustawienia ID rozmówcy."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Przełączono mobilną transmisję danych na: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Możesz to zmienić w dowolnym momencie w Ustawieniach"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Brak komórkowej usługi transmisji danych"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Połączenia alarmowe są niedostępne"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Brak usługi połączeń głosowych"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Pozwala aplikacji na trwałe zapisywanie swoich fragmentów w pamięci. Może to zmniejszyć ilość pamięci dostępnej dla innych aplikacji i spowolnić działanie telefonu."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"uruchom usługę na pierwszym planie"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Zezwala na korzystanie przez aplikację z usług na pierwszym planie."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mierzenie rozmiaru pamięci aplikacji"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Pozwala aplikacji na pobieranie własnego kodu, danych oraz rozmiarów pamięci podręcznej."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modyfikowanie ustawień systemu"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Ta aplikacja może nagrywać dźwięk przy użyciu mikrofonu, gdy jest używana."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"nagrywanie dźwięku w tle"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ta aplikacja może w dowolnym momencie nagrywać dźwięk przy użyciu mikrofonu."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"wysyłanie poleceń do karty SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Pozwala aplikacji na wysyłanie poleceń do karty SIM. To bardzo niebezpieczne."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznawanie aktywności fizycznej"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Zezwala na odczyt przez aplikację plików wideo w pamięci współdzielonej."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"odczyt plików graficznych z pamięci współdzielonej"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Zezwala na odczyt przez aplikację plików graficznych w pamięci współdzielonej."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"odczyt plików obrazów i filmów wybranych przez użytkownika w pamięci współdzielonej"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Zezwala na odczyt przez aplikację plików obrazów i filmów wybranych w pamięci współdzielonej."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modyfikowanie i usuwanie zawartości pamięci współdzielonej"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Zezwala aplikacji na zapis zawartości pamięci współdzielonej."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"wykonywanie/odbieranie połączeń SIP"</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODINSTALUJ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OTWÓRZ MIMO TO"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Wykryto szkodliwą aplikację"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Zezwolić aplikacji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na dostęp do wszystkich dzienników urządzenia?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Zezwól na jednorazowy dostęp"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nie zezwalaj"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu.\n\nWięcej informacji znajdziesz na stronie g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nie pokazuj ponownie"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Aplikacja <xliff:g id="APP_0">%1$s</xliff:g> chce pokazywać wycinki z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edytuj"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Wibracje przy połączeniach i powiadomieniach"</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nie można korzystać z aparatu telefonu na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nie można korzystać z aparatu tabletu na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nie można z tego skorzystać podczas strumieniowania. Użyj telefonu."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Ustawienie domyślne systemu"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 34c0f0c..f4a654b 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"É possível mudar essa opção a qualquer momento nas Configurações"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nenhum serviço móvel de dados"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que o app torne partes de si mesmo persistentes na memória. Pode limitar a memória disponível para outros apps, deixando o telefone mais lento."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"executar serviço em primeiro plano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que o app use serviços em primeiro plano."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que o app recupere o código, os dados e os tamanhos de cache"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar configurações do sistema"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Enquanto está sendo usado, este app pode gravar áudio usando o microfone."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que o app leia arquivos de vídeo do armazenamento compartilhado."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ler arquivos de imagem do armazenamento compartilhado"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que o app leia arquivos de imagem do armazenamento compartilhado."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler arquivos de imagem e vídeo selecionados pelo usuário no armazenamento compartilhado"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que o app leia arquivos de imagem e vídeo que você selecionar no armazenamento compartilhado."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"alterar ou excluir conteúdo do armaz. compartilhado"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que o app grave o conteúdo do armaz. compartilhado."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"fazer/receber chamadas SIP"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR MESMO ASSIM"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"App nocivo detectado"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir o acesso único"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações.\n\nSaiba mais em g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar novamente"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quer mostrar partes do app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Chamadas e notificações farão o dispositivo vibrar"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível acessar a câmera do smartphone pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível acessar a câmera do tablet pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Não é possível acessar esse conteúdo durante o streaming. Tente pelo smartphone."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Padrão do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"CHIP <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 0fb835b..a72cf67 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -396,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que a app torne partes de si mesma persistentes na memória. Isto pode limitar a disponibilidade da memória para outras aplicações, tornando o telemóvel mais lento."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"executar serviço em primeiro plano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que a app utilize serviços em primeiro plano."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir espaço de armazenamento da app"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite à app obter o código, os dados e o tamanhos de cache da mesma"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar as definições do sistema"</string>
@@ -448,6 +496,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta app pode gravar áudio através do microfone enquanto estiver a ser utilizada."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta app pode gravar áudio através do microfone em qualquer altura."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detetar capturas de ecrã de janelas da app"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Esta app vai receber uma notificação quando for tirada uma captura de ecrã enquanto a app estiver a ser usada."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que a app envie comandos para o SIM. Esta ação é muito perigosa."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer a atividade física"</string>
@@ -699,6 +749,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que a app leia ficheiros de vídeo do armazenamento partilhado."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ler ficheiros de imagem do armazenamento partilhado"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que a app leia ficheiros de imagem do armazenamento partilhado."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler ficheiros de imagem e vídeo do armazenamento partilhado selecionados pelo utilizador"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que a app leia ficheiros de imagem e vídeo que selecionar no seu armazenamento partilhado."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modif./elim. os conteúdos do armazenam. partilhado"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que a apl. escreva conteúd. do armazen. partilhado."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"efetuar/receber chamadas SIP"</string>
@@ -2049,12 +2101,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR MESMO ASSIM"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplicação prejudicial detetada"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que a app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aceda a todos os registos do dispositivo?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acesso único"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo deve apenas ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo só deve ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo.\n\nSaiba mais em g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar de novo"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"A app <xliff:g id="APP_0">%1$s</xliff:g> pretende mostrar partes da app <xliff:g id="APP_2">%2$s</xliff:g>."</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"As chamadas e as notificações vibram."</string>
@@ -2295,6 +2341,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível aceder à câmara do telemóvel a partir do dispositivo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível aceder à câmara do tablet a partir do dispositivo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Não é possível aceder a isto durante o streaming. Em alternativa, experimente no telemóvel."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Não é possível ver o ecrã no ecrã durante o streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Predefinição do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARTÃO <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 34c0f0c..f4a654b 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"É possível mudar essa opção a qualquer momento nas Configurações"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nenhum serviço móvel de dados"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que o app torne partes de si mesmo persistentes na memória. Pode limitar a memória disponível para outros apps, deixando o telefone mais lento."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"executar serviço em primeiro plano"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que o app use serviços em primeiro plano."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que o app recupere o código, os dados e os tamanhos de cache"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modificar configurações do sistema"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Enquanto está sendo usado, este app pode gravar áudio usando o microfone."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que o app leia arquivos de vídeo do armazenamento compartilhado."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"ler arquivos de imagem do armazenamento compartilhado"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que o app leia arquivos de imagem do armazenamento compartilhado."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler arquivos de imagem e vídeo selecionados pelo usuário no armazenamento compartilhado"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que o app leia arquivos de imagem e vídeo que você selecionar no armazenamento compartilhado."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"alterar ou excluir conteúdo do armaz. compartilhado"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que o app grave o conteúdo do armaz. compartilhado."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"fazer/receber chamadas SIP"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR MESMO ASSIM"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"App nocivo detectado"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir o acesso único"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações.\n\nSaiba mais em g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar novamente"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quer mostrar partes do app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Chamadas e notificações farão o dispositivo vibrar"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível acessar a câmera do smartphone pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível acessar a câmera do tablet pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Não é possível acessar esse conteúdo durante o streaming. Tente pelo smartphone."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Padrão do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"CHIP <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 88aab00..3f9c0b5 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Nu se asigură accesul la acest serviciu."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nu poți modifica setarea pentru ID-ul apelantului."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"S-a trecut la datele mobile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Poți modifica oricând opțiunea din Setări"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Fără serviciu de date mobile"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Apelurile de urgență nu sunt disponibile"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Fără servicii vocale"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite aplicației să declare persistente în memorie anumite părți ale sale. Acest lucru poate limita memoria disponibilă pentru alte aplicații și poate încetini funcționarea telefonului."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"să ruleze serviciul în prim plan"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite aplicației să utilizeze serviciile din prim-plan."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"măsurare spațiu de stocare al aplicației"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite aplicației să preia dimensiunile codului, ale datelor și ale memoriei cache"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modifică setări de sistem"</string>
@@ -450,6 +496,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Această aplicație poate să înregistreze conținut audio folosind microfonul când este în uz."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"să înregistreze conținut audio în fundal"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Această aplicație poate înregistra conținut audio folosind microfonul oricând."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detectează capturile de ecran din ferestrele aplicației"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Aplicația va fi notificată când se realizează o captură de ecran din folosirea aplicației."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"să trimită comenzi către SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite aplicației să trimită comenzi pe cardul SIM. Această permisiune este foarte periculoasă."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recunoașterea activității fizice"</string>
@@ -701,6 +749,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite aplicației să citească fișiere video din spațiul de stocare comun."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"să citească fișiere imagine din spațiul de stocare comun"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite aplicației să citească fișiere imagine din spațiul de stocare comun."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"să citească fișierele imagine și video selectate de utilizator din spațiul de stocare comun"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite aplicației să citească fișierele imagine și video pe care le selectezi din spațiul de stocare comun."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"să modifice sau să șteargă conținutul spațiului de stocare comun"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite aplicației scrierea conținutul spațiului de stocare comun."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"efectuarea/primirea apelurilor SIP"</string>
@@ -2051,12 +2101,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEZINSTALEAZĂ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"Deschide oricum"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplicație dăunătoare detectată"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permiți ca <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> să acceseze toate jurnalele dispozitivului?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permite accesul o dată"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nu permite"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Jurnalele dispozitivului înregistrează activitatea de pe dispozitivul tău. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. Este posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Jurnalele dispozitivului înregistrează activitatea de pe acesta. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. E posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv.\n\nAflă mai multe la g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nu mai afișa"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vrea să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editează"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Apelurile și notificările vor vibra"</string>
@@ -2297,6 +2341,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nu se poate accesa camera foto a telefonului de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nu se poate accesa camera foto a tabletei de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nu se poate accesa în timpul streamingului. Încearcă pe telefon."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Prestabilit de sistem"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 33bfe77..33612de 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Идентификация абонента по умолчанию не запрещена. След. вызов: разрешена"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга не предоставляется."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Невозможно изменить параметр идентификатора вызывающего абонента."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Используется мобильный интернет <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Этот параметр можно в любой момент изменить в настройках."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильный Интернет недоступен"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Экстренные вызовы недоступны"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Голосовые вызовы недоступны"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Приложение сможет постоянно хранить свои компоненты в памяти. Это может уменьшить объем памяти, доступный другим приложениям, и замедлить работу устройства."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"Запуск активных сервисов"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Разрешить приложению использовать активные сервисы."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"Вычисление объема памяти приложений"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Приложение сможет получать сведения о размере кода, данных и кеша."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"Изменение настроек системы"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Когда приложение используется, оно может записывать аудио с помощью микрофона."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Записывать аудио в фоновом режиме"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Приложение может в любое время записывать аудио с помощью микрофона."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"Отправка команд SIM-карте"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Приложение сможет отправлять команды SIM-карте (данное разрешение представляет большую угрозу)."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"Распознавать физическую активность"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Приложение сможет считывать видеофайлы из общего хранилища."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"считывание изображений из общего хранилища"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Приложение сможет считывать изображения из общего хранилища."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"считывание указанных пользователем изображений и видеофайлов из общего хранилища"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Приложение сможет считывать указанные вами изображения и видеофайлы из общего хранилища."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"Изменение или удаление данных на общем накопителе"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Приложение сможет записывать данные на общий накопитель."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"Входящие и исходящие вызовы SIP"</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"УДАЛИТЬ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ОТКРЫТЬ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Обнаружено вредоносное приложение"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Разрешить приложению \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" доступ ко всем журналам устройства?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешить разовый доступ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Запретить"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Не исключено, что некоторые журналы или сведения на вашем устройстве будут по-прежнему доступны его производителю."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Также некоторые журналы или сведения на вашем устройстве могут быть доступны его производителю.\n\nПодробнее: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больше не показывать"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Приложение \"<xliff:g id="APP_0">%1$s</xliff:g>\" запрашивает разрешение на показ фрагментов приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"."</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Изменить"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Для звонков и уведомлений включен вибросигнал."</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"У устройства <xliff:g id="DEVICE">%1$s</xliff:g> нет доступа к камере телефона."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"У устройства \"<xliff:g id="DEVICE">%1$s</xliff:g>\" нет доступа к камере планшета."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Этот контент недоступен во время трансляции. Используйте телефон."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Системные настройки по умолчанию"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index dd5b817..3eab51b 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"සේවාවන් සපයා නැත."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"අමතන්නාගේ ID සැකසීම ඔබට වෙනස්කල නොහැක."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"දත්ත <xliff:g id="CARRIERDISPLAY">%s</xliff:g> වෙත මාරු කරන ලදි"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"ඔබට සැකසීම් තුළ මෙය ඕනෑම වේලාවක වෙනස් කළ හැක"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ජංගම දත්ත සේවාව නැත"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"හදිසි ඇමතුම් ලබා ගත නොහැකිය"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"හඬ සේවාව නැත"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"යෙදුමට තම කොටස් මතකය තුල නොබිඳීව රඳා පවත්වාගෙන යාමට අවසර දෙන්න. මෙය දුරකථනය මන්දගාමී කරමින් අනෙකුත් උපාංගයන් සඳහා ඉතිරි මතකය සීමා කිරීමට හැක."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"පෙරබිම් සේවාව ධාවනය කරන්න"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"පෙරබිම් සේවා භාවිත කිරීමට යෙදුමට ඉඩ දෙයි."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"යෙදුම් ආචයනයේ ඉඩ ප්රමාණය මැනීම"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"යෙදුමකට එහි කේතය, දත්ත සහ හැඹිලි ප්රමාණ ලබාගැනීමට අවසර දෙන්න."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"පද්ධති සැකසීම් වෙනස් කිරීම"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"මෙම යෙදුමට එය භාවිතයෙහි ඇති අතරතුර මයික්රෆෝනය භාවිත කර ඕඩියෝ පටිගත කිරීමට හැකිය."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"පසුබිමෙහි ඕඩියෝ පටිගත කරන්න"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"මෙම යෙදුමට ඕනෑම වේලාවක මයික්රෆෝනය භාවිත කර ඕඩියෝ පටිගත කිරීමට හැකිය."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM වෙත විධාන යැවීම"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM වෙත විධාන ගෙන යාමට යෙදුමට අවසර දෙයි. මෙය ඉතා භයානක වේ."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ශාරීරික ක්රියාකාරකම හඳුනා ගන්න"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ඔබගේ බෙදා ගත් ගබඩාවෙන් වීඩියෝ ගොනු කියවීමට යෙදුමට ඉඩ දෙයි."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"බෙදා ගත් ගබඩාවෙන් රූප ගොනු කියවන්න"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ඔබගේ බෙදා ගත් ගබඩාවෙන් රූප ගොනු කියවීමට යෙදුමට ඉඩ දෙයි."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"බෙදා ගත් ආචයනයෙන් පරිශීලක තෝරන ලද රූප සහ වීඩියෝ ගොනු කියවන්න"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ඔබේ බෙදා ගත් ආචයනයෙන් ඔබ තෝරන රූප සහ වීඩියෝ ගොනු කියවීමට යෙදුමට ඉඩ දෙයි."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ඔබේ බෙදා ගත් ගබඩාවේ අන්තර්ගත වෙනස් කරන්න නැතහොත් මකන්න"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"යෙදුමට ඔබේ බෙදා ගත් ගබඩාවේ අන්තර්ගත කියවීමට ඉඩ දෙයි."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP ඇමතුම් සිදුකිරීමට/ලබාගැනීමට"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"අස්ථාපනය කරන්න"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"කෙසේ වුවත් විවෘත කරන්න"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"හානිකර යෙදුමක් අනාවරණය කර ගන්නා ලදී"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> හට සියලු උපාංග ලොග ප්රවේශ වීමට ඉඩ දෙන්නද?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"එක් වරක් ප්රවේශය ඉඩ දෙන්න"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ඉඩ නොදෙන්න"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්රවේශ විය හැක."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්රවේශ විය හැක.\n\ng.co/android/devicelogs හි දී තව දැන ගන්න."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"නැවත නොපෙන්වන්න"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට අවශ්යයි"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"සංස්කරණය"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ඇමතුම් සහ දැනුම්දීම් කම්පනය වනු ඇත"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> වෙතින් දුරකථනයේ කැමරාවට ප්රවේශ විය නොහැකිය"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> වෙතින් ටැබ්ලටයේ කැමරාවට ප්රවේශ විය නොහැකිය"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ප්රවාහය කරන අතරේ මෙයට ප්රවේශ විය නොහැක. ඒ වෙනුවට ඔබේ දුරකථනයෙහි උත්සාහ කරන්න."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"පද්ධති පෙරනිමිය"</string>
<string name="default_card_name" msgid="9198284935962911468">"කාඩ්පත <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 40abf79..de86dfe 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"V predvolenom nastavení nie je identifikácia volajúceho obmedzená. Ďalší hovor: Bez obmedzenia"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba nie je poskytovaná."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nemôžete meniť nastavenie identifikácie volajúcich."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Dátové pripojenie bolo prepnuté na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Toto môžete kedykoľvek zmeniť v Nastaveniach"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Žiadna mobilná dátová služba"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Tiesňové volania nie sú k dispozícii"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Žiadne hlasové hovory"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Umožňuje aplikácii uložiť niektoré svoje časti natrvalo do pamäte. Môže to obmedziť pamäť dostupnú pre ostatné aplikácie a spomaliť tak telefón."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"spustiť službu v popredí"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Umožňuje aplikácii používať služby v popredí"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"zistiť veľkosť ukladacieho priestoru aplikácie"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Umožňuje aplikácii načítať svoj kód, údaje a veľkosti vyrovnávacej pamäte"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"upraviť nastavenia systému"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Táto aplikácia môže nahrávať zvuk pomocou mikrofónu, keď ju používate."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"nahrávanie zvuku na pozadí"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Táto aplikácia môže kedykoľvek nahrávať zvuk pomocou mikrofónu."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"posielanie príkazov do SIM karty"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikácii odosielať príkazy na SIM kartu. Toto je veľmi nebezpečné povolenie."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávanie fyzickej aktivity"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Umožňuje aplikácii čítať videosúbory z vášho zdieľaného priestoru."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"čítať súbory obrázka zo zdieľaného priestoru"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Umožňuje aplikácii čítať súbory obrázka z vášho zdieľaného priestoru."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čítanie obrázkových súborov a videosúborov vybraných používateľom v zdieľanom ukladacom priestore"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Umožňuje aplikácii čítať obrázkové súbory a videosúbory, ktoré vyberiete vo svojom zdieľanom ukladacom priestore."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"upravovanie alebo odstraňovanie obsahu zdieľaného úložiska"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Umožňuje aplikácii zapisovať obsah zdieľaného úložiska."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"uskutočňovanie/príjem hovorov SIP"</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODINŠTALOVAŤ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OTVORIŤ AJ TAK"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Bola zistená škodlivá aplikácia"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Chcete povoliť aplikácii <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> prístup k všetkým denníkom zariadenia?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Povoliť jednorazový prístup"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nepovoliť"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení.\n\nViac sa dozviete na g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Už nezobrazovať"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> chce zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Upraviť"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Hovory a upozornenia budú vibrovať"</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere telefónu"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere tabletu"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"K tomuto obsahu nie je počas streamovania prístup. Skúste použiť telefón."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Predvolené systémom"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 53a0321..52c5441 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID klicatelja je ponastavljen na neomejeno. Naslednji klic: ni omejeno"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Storitev ni nastavljena in omogočena."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ne morete spremeniti nastavitve ID-ja klicatelja."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Prenos podatkov je preklopljen na operaterja <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"To lahko kadar koli spremenite v nastavitvah."</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ni mobilne podatkovne storitve"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Klicanje v sili ni na voljo"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ni storitve za glasovne klice"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Aplikaciji omogoča, da nekatere svoje dele naredi trajne v pomnilniku. S tem je lahko pomnilnik omejen za druge aplikacije, zaradi česar je delovanje telefona upočasnjeno."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"Izvajanje storitve v ospredju"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Aplikaciji dovoljuje uporabo storitev v ospredju."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"izračunavanje prostora za shranjevanje aplikacije"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Aplikaciji omogoča, da pridobi njeno kodo, podatke in velikosti predpomnilnika."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"spreminjanje sistemskih nastavitev"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Ta aplikacija lahko uporablja mikrofon za snemanje zvoka med uporabo aplikacije."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snemanje zvoka v ozadju"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ta aplikacija lahko poljubno uporablja mikrofon za snemanje zvoka."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"pošiljanje ukazov na kartico SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Aplikaciji dovoli pošiljanje ukazov kartici SIM. To je lahko zelo nevarno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje telesne dejavnosti"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Aplikaciji omogoča branje videodatotek v deljeni shrambi."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"branje slikovnih datotek v deljeni shrambi"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Aplikaciji omogoča branje slikovnih datotek v deljeni shrambi."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"branje uporabniško izbranih slikovnih datotek in videodatotek v deljeni shrambi"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Aplikaciji omogoča branje slikovnih datotek in videodatotek, ki jih izberete v deljeni shrambi."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"spreminjanje ali brisanje vsebine skupne shrambe"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Aplikaciji omogoča zapisovanje vsebine skupne shrambe."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"opravljanje/sprejemanje klicev SIP"</string>
@@ -1254,7 +1306,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Zagon aplikacij."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Dokončevanje zagona."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Pritisnili ste gumb za vklop, s čimer običajno izklopite zaslon.\n\nPoskusite se narahlo dotakniti med nastavljanjem prstnega odtisa."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Za končanje nastavitve izklopite zaslon"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Za končanje nastavitve izklopite zaslon."</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Izklopi"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Želite nadaljevati preverjanje prstnega odtisa?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"Pritisnili ste gumb za vklop, s čimer običajno izklopite zaslon.\n\nZa preverjanje prstnega odtisa se poskusite narahlo dotakniti."</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODMESTI"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VSEENO ODPRI"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Zaznana je bila škodljiva aplikacija"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Ali aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> dovolite dostop do vseh dnevnikov naprave?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dovoli enkratni dostop"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne dovoli"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi.\n\nPreberite več o tem na g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikaži več"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibriranje bo vklopljeno za klice in obvestila"</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ni mogoče dostopati do fotoaparata telefona prek naprave <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ni mogoče dostopati do fotoaparata tabličnega računalnika prek naprave <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Do te vsebine ni mogoče dostopati med pretočnim predvajanjem. Poskusite s telefonom."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Sistemsko privzeto"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 6c3f145..d5c8c87 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të pakufizuar. Telefonata e radhës: e pakufizuar!"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Shërbimi nuk është përgatitur."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nuk mund ta ndryshosh cilësimin e ID-së së telefonuesit."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Të dhënat u kaluan te <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Mund ta ndryshosh këtë në çdo kohë te \"Cilësimet\""</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nuk ka shërbim të të dhënave celulare"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Telefonatat e urgjencës nuk ofrohen"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nuk ka shërbim zanor"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Lejon aplikacionin të zaptojë një pjesë të qëndrueshme në kujtesë. Kjo mund të kufizojë kujtesën e disponueshme për aplikacionet e tjera duke e ngadalësuar telefonin."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ekzekuto shërbimin në plan të parë"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Lejon aplikacionin të përdorë shërbimet në plan të parë."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mat hapësirën ruajtëse të aplikacionit"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Lejon aplikacionin të gjejë kodin e tij, të dhënat dhe madhësitë e memorieve të përkohshme."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modifiko cilësimet e sistemit"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Ky aplikacion mund të regjistrojë audion duke përdorur mikrofonin kur aplikacioni është në përdorim."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"të regjistrojë audion në sfond"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ky aplikacion mund të regjistrojë audion me mikrofonin në çdo kohë."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"dërgo komanda te karta SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Lejon aplikacionin t\'i dërgojë komanda kartës SIM. Kjo është shumë e rrezikshme."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"njih aktivitetin fizik"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Lejon që aplikacioni të lexojë skedarët e videove nga hapësira ruajtëse e ndarë."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"të lexojë skedarët e imazheve nga hapësira ruajtëse e ndarë"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Lejon që aplikacioni të lexojë skedarët e imazheve nga hapësira ruajtëse e ndarë."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"të lexojë imazhin e zgjedhur nga përdoruesi dhe skedarët e videove nga hapësira ruajtëse e ndarë"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Lejon aplikacionin të lexojë imazhin dhe skedarët e videove që ti zgjedh nga hapësira jote ruajtëse e ndarë."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"modifiko ose fshi përmbajtjet e hapësirës ruajtëse të ndarë"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Lejon që aplikacioni të shkruajë përmbajtjet e hapësirës ruajtëse të ndarë."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"bëj/merr telefonata SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ÇINSTALO"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"HAPE GJITHSESI"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"U gjet aplikacion i dëmshëm"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Të lejohet që <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> të ketë qasje te të gjitha evidencat e pajisjes?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Lejo qasjen vetëm për një herë"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Mos lejo"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde.\n\nMëso më shumë në g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Mos e shfaq më"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> dëshiron të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Modifiko"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Do të lëshojë dridhje për telefonatat dhe njoftimet"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nuk mund të qasesh në kamerën e telefonit tënd nga <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nuk mund të qasesh në kamerën e tabletit tënd nga <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nuk mund të kesh qasje në të gjatë transmetimit. Provoje në telefon më mirë."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Parazgjedhja e sistemit"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index d23b056..142d2dd 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -73,10 +73,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ИД позиваоца подразумевано није ограничен. Следећи позив: Није ограничен."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга није добављена."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Не можете да промените подешавање ИД-а корисника."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилни подаци су пребачени на оператера <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Ово можете у сваком тренутку да промените у Подешавањима"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Нема услуге мобилних података"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Хитни позиви нису доступни"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Нема гласовне услуге"</string>
@@ -398,6 +396,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дозвољава апликацији да учини сопствене компоненте трајним у меморији. Ово може да ограничи меморију доступну другим апликацијама и успори телефон."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"покрени услугу у првом плану"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Дозвољава апликацији да користи услуге у првом плану."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"мерење меморијског простора у апликацији"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Дозвољава апликацији да преузме величине кôда, података и кеша."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"измена подешавања система"</string>
@@ -450,6 +496,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Ова апликација може да снима звук помоћу микрофона док се апликација користи."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"да снима звук у позадини"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ова апликација може да снима звук помоћу микрофона у било ком тренутку."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"слање команди на SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Омогућава апликацији да шаље команде SIM картици. То је веома опасно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавање физичких активности"</string>
@@ -701,6 +751,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Омогућава апликацији да чита видео фајлове из дељеног меморијског простора."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"читање фајлова слика из дељеног меморијског простора"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Омогућава апликацији да чита фајлове слика из дељеног меморијског простора."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"читање фајлова слика и видео снимака које корисник бира из дељеног меморијског простора"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Омогућава апликацији да чита фајлове слика и видео снимака које изаберете из дељеног меморијског простора."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"мењање или брисање садржаја дељеног меморијског простора"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Дозвољава апликацији да уписује садржај дељеног меморијског простора."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"упућивање/пријем SIP позива"</string>
@@ -2051,12 +2103,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ДЕИНСТАЛИРАЈ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ИПАК ОТВОРИ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Откривена је штетна апликација"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Желите да дозволите апликацији <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> да приступа свим евиденцијама уређаја?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дозволи једнократан приступ"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволи"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају.\n\nСазнајте више на g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Не приказуј поново"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Апликација <xliff:g id="APP_0">%1$s</xliff:g> жели да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Измени"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Вибрација за позиве и обавештења је укључена"</string>
@@ -2297,6 +2343,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не може да се приступи камери телефона са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не може да се приступи камери таблета са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Овом не можете да приступате током стримовања. Пробајте на телефону."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Подразумевани системски"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТИЦА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index ee6a8ac..baeffd2 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummerpresentatörens standardinställning är inte blockerad. Nästa samtal: Inte blockerad"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjänsten är inte etablerad."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Det går inte att ändra inställningen för nummerpresentatör."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Du har bytt mobildata till <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Du kan ändra det här när som helst i inställningarna"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjänst"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Det går inte att ringa nödsamtal"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tjänsten för röstsamtal har blockerats"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Tillåter att delar av appen läggs beständigt i minnet. Detta kan innebära att det tillgängliga minnet för andra appar begränsas, vilket gör mobilen långsam."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"kör tjänst i förgrunden"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Tillåter att appen använder tjänster i förgrunden."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"mäta appens lagringsplats"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Tillåter att appen hämtar kod, data och cachestorlekar"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ändra systeminställningar"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Appen kan ta spela in ljud med mikrofonen när appen används."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"spela in ljud i bakgrunden"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Appen kan spela in ljud med mikrofonen när som helst."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"skicka kommandon till SIM-kortet"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillåter att appen skickar kommandon till SIM-kortet. Detta är mycket farligt."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"känn igen fysisk aktivitet"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Tillåter att appen läser videofiler från delad lagring."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"läsa bildfiler från delad lagring"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Tillåter att appen läser bildfiler från delad lagring."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"läsa bild- och videofiler som användaren valt från delat lagringsutrymme"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Tillåter att appen läser bild- och videofiler som du väljer från ditt delade lagringsutrymme."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"ändra eller ta bort innehåll på delat lagringsutrymme"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Tillåter att appen skriver innehåll på ditt delade lagringsutrymme."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"gör/ta emot SIP-anrop"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"AVINSTALLERA"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ÖPPNA ÄNDÅ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"En skadlig app har upptäckts"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vill du tillåta att <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> får åtkomst till alla enhetsloggar?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tillåt engångsåtkomst"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tillåt inte"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"I enhetsloggar registreras vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhetsloggar registrerar vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten.\n\nLäs mer på g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Visa inte igen"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill kunna visa bitar av <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Redigera"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibrerar vid samtal och aviseringar"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Telefonens kamera kan inte användas från <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Surfplattans kamera kan inte användas från <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Det går inte att komma åt innehållet när du streamar. Testa med telefonen i stället."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Systemets standardinställning"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7b9d89f..ea63885 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Chaguomsingi za ID ya mpigaji simu za kutozuia. Simu ifuatayo: Haijazuiliwa"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Huduma haitathminiwi."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Hauwezi kubadilisha mpangilio wa kitambulisho cha anayepiga."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Sasa unatumia data ya mtandao wa <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Unaweza kubadilisha hali hii wakati wowote kwenye Mipangilio"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Hakuna huduma ya data kwa vifaa vya mkononi"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Huduma ya kupiga simu za dharura haipatikani"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hakuna huduma za simu za sauti"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Inaruhusu programu kuendelesha vijisehemu vyake kwenye kumbukumbu. Hii inaweza kupunguza kumbukumbu inayopatikana katika programu nyingine ikipunguza kasi ya simu."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"tumia huduma zinazoonekana kwenye skrini"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Huruhusu programu kutumia huduma zinazoonekana kwenye skrini."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"Pima nafasi ya hifadhi ya programu"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Huruhusu Programu kupata tena msimbo, data na ukubwa wa akiba yake"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"rekebisha mipangilio ya mfumo"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati programu inatumika."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rekodi sauti chinichini"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati wowote."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"tuma amri kwenye SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Huruhusu programu kutuma amri kwa SIM. Hii ni hatari sana."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"itambue shughuli unazofanya"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Huruhusu programu kusoma faili za video kutoka kwenye hifadhi unayoshiriki."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"soma faili za picha kutoka kwenye hifadhi iliyoshirikiwa"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Huruhusu programu kusoma faili za picha kutoka kwenye hifadhi yako iliyoshirikiwa."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"soma faili za picha na video alizochagua mtumiaji kutoka kwenye hifadhi inayoshirikiwa"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Huruhusu programu kusoma faili za picha na video unazochagua kutoka kwenye hifadhi yako inayoshirikiwa."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"irekebishe au ifute maudhui ya hifadhi unayoshiriki"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Huruhusu programu iandike maudhui ya hifadhi unayoshiriki."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"piga/pokea simu za SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ONDOA"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"FUNGUA TU"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Imetambua programu hatari"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Ungependa kuruhusu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ifikie kumbukumbu zote za kifaa?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Ruhusu ufikiaji wa mara moja"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Usiruhusu"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Kumbukumbu za kifaa zinarekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Kumbukumbu za kifaa hurekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako.\n\nPata maelezo zaidi kwenye g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Usionyeshe tena"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> inataka kuonyesha vipengee <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Badilisha"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Itatetema arifa ikitumwa au simu ikipigwa"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Huwezi kufikia kamera ya simu kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Haiwezi kufikia kamera ya kompyuta kibao kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Huwezi kufikia maudhui haya unapotiririsha. Badala yake jaribu kwenye simu yako."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Chaguomsingi la mfumo"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM KADI <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 2a0fc2c..461ddbd 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்படவில்லை என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்படவில்லை"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"சேவை ஒதுக்கப்படவில்லை."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"அழைப்பாளர் ஐடி அமைப்பை மாற்ற முடியாது."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"டேட்டா <xliff:g id="CARRIERDISPLAY">%s</xliff:g>க்கு மாற்றப்பட்டது"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"அமைப்புகளில் இதை எப்போது வேண்டுமானாலும் மாற்றிக்கொள்ளலாம்"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"மொபைல் டேட்டா சேவையைப் பயன்படுத்த முடியாது"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"அவசர அழைப்பைச் செய்ய முடியாது"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"குரல் சேவை இல்லை"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"நினைவகத்தில் நிலையாக இருக்கும் தன்னுடைய பகுதிகளை உருவாக்கப் ஆப்ஸை அனுமதிக்கிறது. இதனால பிற பயன்பாடுகளுக்குக் கிடைக்கும் நினைவகம் வரையறுக்கப்பட்டு, மொபைலின் வேகத்தைக் குறைக்கலாம்"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"முன்புலத்தில் இயங்கும் சேவையை இயக்குதல்"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"முன்புலத்தில் இயங்கும் சேவைகளை உபயோகிக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ஆப்ஸ் சேமிப்பு இடத்தை அளவிடல்"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ஆப்ஸ், அதன் குறியீடு, தரவு, மற்றும் தற்காலிகச் சேமிப்பு அளவுகளை மீட்டெடுக்க அனுமதிக்கிறது"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"சாதன அமைப்புகளை மாற்றுதல்"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"இந்த ஆப்ஸ் உபயோகத்தில் இருக்கும்போதே இதனால் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்ய முடியும்."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"பின்புலத்தில் ஆடியோ ரெக்கார்டு செய்தல்"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"இந்த ஆப்ஸால் எப்போது வேண்டுமானாலும் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்ய முடியும்."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"கட்டளைகளை சிம்மிற்கு அனுப்புதல்"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"சிம் க்குக் கட்டளைகளை அனுப்ப ஆப்ஸை அனுமதிக்கிறது. இது மிகவும் ஆபத்தானதாகும்."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"உடல் செயல்பாட்டைக் கண்டறிதல்"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"உங்கள் பகிர்ந்த சேமிப்பகத்திலுள்ள வீடியோ ஃபைல்களைப் படிக்க ஆப்ஸை அனுமதிக்கும்."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"பகிர்ந்த சேமிப்பகத்திலுள்ள பட ஃபைல்களைப் படித்தல்"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"உங்கள் பகிர்ந்த சேமிப்பகத்திலுள்ள பட ஃபைல்களைப் படிக்க ஆப்ஸை அனுமதிக்கும்."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"பகிர்ந்த சேமிப்பகத்தில் இருந்து பயனர் தேர்ந்தெடுக்கும் பட/வீடியோ ஃபைல்களைப் படித்தல்"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"உங்கள் பகிர்ந்த சேமிப்பகத்தில் இருந்து நீங்கள் தேர்ந்தெடுக்கும் பட/வீடியோ ஃபைல்களைப் படிக்க ஆப்ஸை அனுமதிக்கும்."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"பகிர்ந்த சேமிப்பகத்தின் உள்ளடக்கங்களை மாற்றும் அல்லது நீக்கும்"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"பகிர்ந்த சேமிப்பகத்தின் உள்ளடக்கத்தில் மாற்றங்களைச் செய்ய அனுமதிக்கும்."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP அழைப்புகளைச் செய்தல்/பெறுதல்"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"நிறுவல் நீக்கு"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"பரவாயில்லை, திற"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"தீங்கிழைக்கும் ஆப்ஸ் உள்ளது"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"சாதனப் பதிவுகள் அனைத்தையும் <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> அணுக அனுமதிக்கவா?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ஒருமுறை அணுகலை அனுமதி"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"அனுமதிக்க வேண்டாம்"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸ் இந்தப் பதிவுகளைப் பயன்படுத்தலாம்.\n\nபாதுகாக்கப்பட வேண்டிய தகவல்கள் சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக நீங்கள் நம்பும் ஆப்ஸை மட்டும் அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸால் இந்தப் பதிவுகளைப் பயன்படுத்த முடியும்.\n\nபாதுகாக்கப்பட வேண்டிய தகவல்கள், சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக உங்களுக்கு நம்பகமான ஆப்ஸை மட்டும் அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை நீங்கள் அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்.\n\n மேலும் அறிந்துகொள்ள g.co/android/devicelogs இணைப்பிற்குச் செல்லுங்கள்."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"மீண்டும் காட்டாதே"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP_0">%1$s</xliff:g> அனுமதி கேட்கிறது"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"திருத்து"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"அழைப்புகள் மற்றும் அறிவிப்புகளுக்கு அதிரும்"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்திலிருந்து மொபைலின் கேமராவை அணுக முடியாது"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்திலிருந்து டேப்லெட்டின் கேமராவை அணுக முடியாது"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ஸ்ட்ரீமின்போது இதை அணுக முடியாது. அதற்குப் பதிலாக உங்கள் மொபைலில் பயன்படுத்திப் பார்க்கவும்."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"சிஸ்டத்தின் இயல்பு"</string>
<string name="default_card_name" msgid="9198284935962911468">"கார்டு <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index a7c2d4e..37d4328 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"కాలర్ ID ఆటోమేటిక్లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి లేదు"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"సేవ కేటాయించబడలేదు."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"మీరు కాలర్ ID సెట్టింగ్ను మార్చలేరు."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"డేటాను <xliff:g id="CARRIERDISPLAY">%s</xliff:g>కు స్విచ్ చేశారు"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"మీరు ఎప్పుడైనా సెట్టింగ్లలో దీనిని మార్చవచ్చు"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"మొబైల్ డేటా సేవ లేదు"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"అత్యవసర కాలింగ్ అందుబాటులో లేదు"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"వాయిస్ సర్వీస్ లేదు"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"యాప్, దాని భాగాలు మెమరీలో ఉండేలా చేయడానికి దానిని అనుమతిస్తుంది. ఇది ఇతర యాప్లకు అందుబాటులో ఉన్న మెమరీని ఆక్రమిస్తుంది, ఫోన్ నెమ్మదిగా పని చేస్తుంది."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"సేవని ముందు భాగంలో అమలు చేయడం"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ముందు భాగంలో సేవలను ఉపయోగించడానికి యాప్ని అనుమతిస్తుంది."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"యాప్ నిల్వ స్థలాన్ని అంచనా వేయడం"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"యాప్ కోడ్, డేటా మరియు కాష్ పరిమాణాలను తిరిగి పొందడానికి దాన్ని అనుమతిస్తుంది"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"సిస్టమ్ సెట్టింగ్లను మార్చడం"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"యాప్ ఉపయోగంలో ఉన్నపుడు మైక్రోఫోన్ను ఉపయోగించి ఈ యాప్, ఆడియోను రికార్డ్ చేయగలదు."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"బ్యాక్గ్రౌండ్లో ఆడియోను రికార్డ్ చేయగలదు"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"మైక్రోఫోన్ను ఉపయోగించి ఈ యాప్ ఎప్పుడైనా ఆడియోను రికార్డ్ చేయగలదు."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIMకి ఆదేశాలను పంపడం"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"సిమ్కు ఆదేశాలను పంపడానికి యాప్ను అనుమతిస్తుంది. ఇది చాలా ప్రమాదకరం."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"భౌతిక కార్యాకలాపాన్ని గుర్తించండి"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"మీ షేర్ చేయబడిన స్టోరేజ్ నుండి వీడియో ఫైల్లను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"షేర్ చేయబడిన స్టోరేజ్ నుండి ఇమేజ్ ఫైల్లను చదవండి"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"మీ షేర్ చేయబడిన స్టోరేజ్ నుండి ఇమేజ్ ఫైల్లను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"షేర్ చేయబడిన స్టోరేజ్ నుండి యూజర్ ఎంచుకున్న ఇమేజ్ ఫైల్స్ను, వీడియో ఫైల్స్ను చదువుతుంది"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"మీరు మీ షేర్ చేయబడిన స్టోరేజ్ నుండి ఎంచుకున్న ఇమేజ్, వీడియో ఫైల్స్ను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"మీ షేర్ చేసిన నిల్వ యొక్క కంటెంట్లను ఎడిట్ చేయండి లేదా తొలగించండి"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"మీ షేర్ చేసిన నిల్వ యొక్క కంటెంట్లను రాయడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP కాల్స్ను చేయడానికి/స్వీకరించడానికి"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"అన్ఇన్స్టాల్ చేయండి"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ఏదేమైనా తెరువు"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"హానికరమైన యాప్ గుర్తించబడింది"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"అన్ని పరికర లాగ్లను యాక్సెస్ చేయడానికి <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ను అనుమతించాలా?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"వన్-టైమ్ యాక్సెస్ను అనుమతించండి"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"అనుమతించవద్దు"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్లు ఈ లాగ్లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్లను మాత్రమే అన్ని పరికర లాగ్లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్ను అనుమతించకపోతే, అది తన స్వంత లాగ్లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్లు ఈ లాగ్లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్లను మాత్రమే అన్ని పరికర లాగ్లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్ను అనుమతించకపోతే, అది తన స్వంత లాగ్లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు.\n\ng.co/android/devicelogsలో దీని గురించి మరింత తెలుసుకోండి."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"మళ్లీ చూపవద్దు"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> స్లైస్లను చూపించాలనుకుంటోంది"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ఎడిట్ చేయండి"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"కాల్స్ మరియు నోటిఫికేషన్లు వైబ్రేట్ అవుతాయి"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"మీ <xliff:g id="DEVICE">%1$s</xliff:g> నుండి ఫోన్ కెమెరాను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"మీ <xliff:g id="DEVICE">%1$s</xliff:g> నుండి టాబ్లెట్ కెమెరాను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"స్ట్రీమింగ్ చేస్తున్నప్పుడు దీన్ని యాక్సెస్ చేయడం సాధ్యపడదు. బదులుగా మీ ఫోన్లో ట్రై చేయండి."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"సిస్టమ్ ఆటోమేటిక్ సెట్టింగ్"</string>
<string name="default_card_name" msgid="9198284935962911468">"కార్డ్ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index c0ab275..81c1d39 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"อนุญาตให้แอปพลิเคชันทำให้ส่วนหนึ่งของตัวเองคงอยู่ถาวรในหน่วยความจำ ซึ่งจะจำกัดพื้นที่หน่วยความจำที่ใช้งานได้ของแอปพลิเคชันอื่นๆ และทำให้โทรศัพท์ทำงานช้าลง"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"เรียกใช้บริการที่ใช้งานอยู่"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ใช้งานอยู่"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"วัดพื้นที่เก็บข้อมูลของแอปพลิเคชัน"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"อนุญาตให้แอปพลิเคชันเรียกดูรหัส ข้อมูล และขนาดแคชของตน"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"แก้ไขการตั้งค่าระบบ"</string>
@@ -447,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"แอปนี้บันทึกเสียงด้วยไมโครโฟนขณะที่มีการใช้แอปได้"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"บันทึกเสียงในเบื้องหลัง"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"แอปนี้บันทึกเสียงด้วยไมโครโฟนได้ทุกเมื่อ"</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ตรวจจับการจับภาพหน้าจอของหน้าต่างแอป"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"แอปนี้จะได้รับการแจ้งเตือนเมื่อมีการจับภาพหน้าจอขณะใช้งานแอป"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ส่งคำสั่งไปยังซิม"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"อนุญาตให้แอปส่งคำสั่งไปยัง SIM ซึ่งอันตรายมาก"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"จดจำกิจกรรมการเคลื่อนไหวร่างกาย"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"อนุญาตให้แอปอ่านไฟล์วิดีโอจากพื้นที่เก็บข้อมูลที่แชร์"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"อ่านไฟล์ภาพจากพื้นที่เก็บข้อมูลที่แชร์"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"อนุญาตให้แอปอ่านไฟล์ภาพจากพื้นที่เก็บข้อมูลที่แชร์"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"อ่านไฟล์รูปภาพและวิดีโอที่ผู้ใช้เลือกจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"อนุญาตให้แอปอ่านไฟล์รูปภาพและวิดีโอที่คุณเลือกจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"แก้ไขหรือลบเนื้อหาในพื้นที่จัดเก็บข้อมูลที่ใช้ร่วมกัน"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"อนุญาตให้แอปเขียนเนื้อหาในพื้นที่จัดเก็บข้อมูลที่ใช้ร่วมกัน"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"โทร/รับสาย SIP"</string>
@@ -2048,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ถอนการติดตั้ง"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"เปิดต่อไป"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ตรวจพบแอปที่เป็นอันตราย"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"อนุญาตให้ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> เข้าถึงบันทึกทั้งหมดของอุปกรณ์ใช่ไหม"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"อนุญาตสิทธิ์เข้าถึงแบบครั้งเดียว"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ไม่อนุญาต"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ \n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ของคุณได้"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์\n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ได้\n\nดูข้อมูลเพิ่มเติมที่ g.co/android/devicelogs"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ไม่ต้องแสดงอีก"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ต้องการแสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"แก้ไข"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"สายเรียกเข้าและการแจ้งเตือนจะสั่น"</string>
@@ -2294,6 +2340,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"เข้าถึงกล้องของโทรศัพท์จาก <xliff:g id="DEVICE">%1$s</xliff:g> ไม่ได้"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"เข้าถึงกล้องของแท็บเล็ตจาก <xliff:g id="DEVICE">%1$s</xliff:g> ไม่ได้"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"เข้าถึงเนื้อหานี้ไม่ได้ขณะที่สตรีมมิง โปรดลองเข้าถึงในโทรศัพท์แทน"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ดูการแสดงภาพซ้อนภาพขณะสตรีมไม่ได้"</string>
<string name="system_locale_title" msgid="711882686834677268">"ค่าเริ่มต้นของระบบ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ซิมการ์ด <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7b9807a..46783ea 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -395,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Pinapayagan ang app na panatilihin ang ilang bahagi nito sa memory. Maaari nitong limitahan ang memory na available sa iba pang apps na nagpapabagal sa telepono."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"paganahin ang foreground na serbisyo"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Payagan ang app na gamitin ang mga foreground na serbisyo."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"sukatin ang espasyo ng storage ng app"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Pinapayagan ang app na bawiin ang code, data, at mga laki ng cache nito"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"baguhin ang mga setting ng system"</string>
@@ -447,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Makakapag-record ng audio ang app na ito gamit ang mikropono habang ginagamit ang app."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"mag-record ng audio sa background"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Makakapag-record ng audio ang app na ito gamit ang mikropono anumang oras."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"i-detect ang mga pag-screen capture ng mga window ng app"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Maaabisuhan ang app na ito kapag may kinuhang screenshot habang ginagamit ang app."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"magpadala ng mga command sa SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Pinapahintulutang magpadala ang app ng mga command sa SIM. Napakapanganib nito."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"tukuyin ang pisikal na aktibidad"</string>
@@ -698,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Nagbibigay-daan sa app na magbasa ng mga video file mula sa iyong nakabahaging storage."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"magbasa ng mga image file mula sa nakabahaging storage"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Nagbibigay-daan sa app na magbasa ng mga image file mula sa iyong nakabahaging storage."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"magbasa ng mga image at video file na pinili ng user mula sa nakabahaging storage"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Nagbibigay-daan sa app na magbasa ng mga image at video file na pipiliin mo mula sa iyong nakabahaging storage."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"baguhin o i-delete ang content ng nakabahagi mong storage"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Pinapayagan ang app na mag-write sa content ng nakabahagi mong storage."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"magsagawa/tumanggap ng mga tawag sa SIP"</string>
@@ -2048,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"I-UNINSTALL"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"BUKSAN PA RIN"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"May na-detect na mapaminsalang app"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Payagan ang <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na i-access ang lahat ng log ng device?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Payagan ang isang beses na pag-access"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Huwag payagan"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo.\n\nMatuto pa sa g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Huwag ipakita ulit"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"Gustong ipakita ng <xliff:g id="APP_0">%1$s</xliff:g> ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"I-edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Magva-vibrate ang mga tawag at notification"</string>
@@ -2294,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Hindi ma-access ang camera ng telepono mula sa iyong <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Hindi ma-access ang camera ng tablet mula sa iyong <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Hindi ito puwedeng i-access habang nagsi-stream. Subukan na lang sa iyong telepono."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Default ng system"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 3459cb0..87d7ba7 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Arayan kimliği varsayılanları kısıtlanmamıştır. Sonraki çağrı: Kısıtlanmamış"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Hizmet sağlanamadı."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Arayanın kimliği ayarını değiştiremezsiniz."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Veriler şuraya aktarıldı: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Bunu istediğiniz zaman Ayarlar\'da değiştirebilirsiniz"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil veri hizmeti yok"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Acil durum çağrısı kullanılamaz"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sesli çağrı hizmeti yok"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Uygulamaya kendisinin bir bölümünü bellekte kalıcı yapma izni verir. Bu izin, diğer uygulamaların kullanabileceği belleği sınırlandırarak telefonun yavaş çalışmasına neden olabilir."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"ön plan hizmetini çalıştırma"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Uygulamanın ön plan hizmetlerinden faydalanmasına izin verir."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"uygulama depolama alanını ölç"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Uygulamaya kodunu, verilerini ve önbellek boyutlarını alma izni verir"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"sistem ayarlarını değiştirme"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu uygulama, kullanıldığı sırada mikrofonu kullanarak ses kaydedebilir."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"arka planda ses kaydeder"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu uygulama, herhangi bir zaman mikrofonu kullanarak ses kaydedebilir."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM karta komut gönderme"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Uygulamanın SIM karta komut göndermesine izin verir. Bu izin çok tehlikelidir."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziksel aktiviteyi algıla"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Uygulamaya, paylaşılan depolama alanınızdaki video dosyalarını okuma izni verir."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"paylaşılan depolama alanınızdaki resim dosyalarını okuma"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Uygulamaya, paylaşılan depolama alanınızdaki resim dosyalarını okuma izni verir."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"kullanıcının, paylaşılan depolama alanından seçtiği resim ve video dosyalarını okuma"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Uygulamaya, paylaşılan depolama alanınızdan seçtiğiniz resim ve video dosyalarını okuma izni verir."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"paylaşılan depolama alanımın içeriğini değiştir veya sil"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Uygulamanın paylaşılan depolama alanınıza içerik yazmasına izin verir."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP aramaları yapma/alma"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"YÜKLEMEYİ KALDIR"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"YİNE DE AÇ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Zararlı uygulama tespit edildi"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> uygulamasının tüm cihaz günlüklerine erişmesine izin verilsin mi?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tek seferlik erişim izni ver"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"İzin verme"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir.\n\nDaha fazla bilgiyi g.co/android/devicelogs sayfasında bulabilirsiniz."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Bir daha gösterme"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> uygulaması, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermek istiyor"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Düzenle"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Aramalar ve bildirimler titreşim yapacak"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan telefonun kamerasına erişilemiyor"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan tabletin kamerasına erişilemiyor"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Canlı oynatılırken bu içeriğe erişilemez. Bunun yerine telefonunuzu kullanmayı deneyin."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Sistem varsayılanı"</string>
<string name="default_card_name" msgid="9198284935962911468">"KART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 2483a30..7a81546 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -74,10 +74,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ідентиф. абонента за умовч. не обмеж. Наст. дзвінок: не обмежений"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Службу не ініціалізовано."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ви не можете змінювати налаштування ідентифікатора абонента."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобільний Інтернет переключено на <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Цей параметр можна будь-коли змінити в налаштуваннях"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Службу передавання мобільних даних заблоковано"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Екстрені виклики недоступні"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Немає голосової служби"</string>
@@ -399,6 +397,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дозволяє програмі робити свої частини сталими в пам’яті. Це може зменшувати обсяг пам’яті, доступної для інших програм, і сповільнювати роботу телефону."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"запускати пріоритетну службу"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Додаток може використовувати пріоритетні служби."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"визначати об’єм пам’яті програми"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Дозволяє програмі отримувати її код, дані та розміри кеш-пам’яті"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"змінювати налаштування системи"</string>
@@ -451,6 +497,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Цей додаток може записувати звук за допомогою мікрофона, коли ви використовуєте його."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"записувати звук у фоновому режимі"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Цей додаток може будь-коли записувати звук за допомогою мікрофона."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"надсилати команди на SIM-карту"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Дозволяє програмі надсилати команди на SIM-карту. Це дуже небезпечно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"розпізнавати фізичну активність"</string>
@@ -702,6 +752,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Дозволяє додатку зчитувати відеофайли з вашого спільного сховища."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"зчитувати файли зображень зі спільного сховища"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Дозволяє додатку зчитувати файли зображень із вашого спільного сховища."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"зчитувати вибрані користувачем файли зображень і відео зі спільного сховища"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Дозволяє додатку зчитувати вибрані вами файли зображень і відео зі спільного сховища."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"змінювати чи видаляти вміст у спільній пам’яті"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Додаток може писати вміст у спільній пам’яті."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"здійснювати й отримувати дзвінки через протокол SIP"</string>
@@ -2052,12 +2104,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ВИДАЛИТИ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"УСЕ ОДНО ВІДКРИТИ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Виявлено шкідливий додаток"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Надати додатку <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> доступ до всіх журналів пристрою?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Надати доступ лише цього разу"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволяти"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому.\n\nДокладніше: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Більше не показувати"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> хоче показати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Редагувати"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Вібросигнал для викликів і сповіщень увімкнено"</string>
@@ -2298,6 +2344,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не вдається отримати доступ до камери телефона з пристрою <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не вдається отримати доступ до камери планшета з пристрою <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Цей контент недоступний під час потокового передавання. Спробуйте натомість скористатися телефоном."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Налаштування системи за умовчанням"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТКА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 7de793c..69bf5e3 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"سروس فراہم نہیں کی گئی۔"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"آپ کالر ID کی ترتیبات تبدیل نہیں کر سکتے ہیں۔"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"ڈیٹا <xliff:g id="CARRIERDISPLAY">%s</xliff:g> پر سوئچ کیا گیا"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"آپ اسے ترتیبات میں کسی بھی وقت تبدیل کر سکتے ہیں"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"کوئی موبائل ڈیٹا سروس دستیاب نہیں ہے"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ہنگامی کالنگ دستیاب نہیں ہے"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"کوئی صوتی سروس نہیں"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ایپ کو خود اپنے ہی حصوں کو میموری میں استقلال پذیر بنانے کی اجازت دیتا ہے۔ یہ فون کو سست بناکر دوسری ایپس کیلئے دستیاب میموری کو محدود کرسکتا ہے۔"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"پیش منظر سروس چلائیں"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"ایپ کو پیش منظر سروسز کے استعمال کی اجازت دیتا ہے۔"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ایپ اسٹوریج کی جگہ کی پیمائش کریں"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ایپ کو اپنے کوڈ، ڈیٹا اور کیش کے سائزوں کی بازیافت کرنے دیتا ہے"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"سسٹم کی ترتیبات میں ترمیم کریں"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"ایپ کے استعمال ہونے کے دوران یہ ایپ مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"پس منظر میں آڈیو ریکارڈ کریں"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"یہ ایپ کسی بھی وقت مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM کو ہدایات بھیجیں"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ایپ کو SIM کو کمانڈز بھیجنے کی اجازت دیتا ہے۔ یہ بہت خطرناک ہے۔"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"جسمانی سرگرمی کی شناخت کریں"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ایپ کو آپ کی اشتراک کردہ اسٹوریج سے ویڈیو فائلز کو پڑھنے کی اجازت دیتا ہے۔"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"اشتراک کردہ اسٹوریج سے تصویری فائلز کو پڑھیں"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"ایپ کو آپ کی اشتراک کردہ اسٹوریج سے تصویری فائلز کو پڑھنے کی اجازت دیتا ہے۔"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"مشترکہ اسٹوریج سے صارف کی منتخب کردہ تصویر اور ویڈیو فائلز کو پڑھیں"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ایپ کو ان تصویر اور ویڈیو فائلز کو پڑھنے کی اجازت دیتی ہے جنہیں آپ اپنے مشترکہ اسٹوریج سے منتخب کرتے ہیں۔"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"اپنے اشتراک کردہ اسٹوریج کے مواد میں ترمیم کریں یا اسے حذف کریں"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ایپ کو آپ کے اشتراک کردہ اسٹوریج کے مواد کو لکھنے کی اجازت دیتا ہے۔"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP کالز کریں/موصول کریں"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"اَن انسٹال کریں"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"بہر صورت کھولیں"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"ضرر رساں ایپ کا پتہ چلا"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> کو آلے کے تمام لاگز تک رسائی کی اجازت دیں؟"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"یک وقتی رسائی کی اجازت دیں"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازت نہ دیں"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنے بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنی بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔\n\ng.co/android/devicelogs پر مزید جانیں۔"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوبارہ نہ دکھائیں"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانا چاہتی ہے"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ترمیم کریں"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"کالز اور اطلاعات پر وائبریٹ کرے گا"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> سے فون کے کیمرا تک رسائی حاصل نہیں کی جا سکتی"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> سے ٹیبلیٹ کے کیمرا تک رسائی حاصل نہیں کی جا سکتی"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"سلسلہ بندی کے دوران اس تک رسائی حاصل نہیں کی جا سکتی۔ اس کے بجائے اپنے فون پر کوشش کریں۔"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"سسٹم ڈیفالٹ"</string>
<string name="default_card_name" msgid="9198284935962911468">"کارڈ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 8c88585..c5e6f57 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklanmagan. Keyingi qo‘ng‘iroq: cheklanmagan"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Xizmat ishalamaydi."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Qo‘ng‘iroq qiluvchining ID raqami sozlamasini o‘zgartirib bo‘lmaydi."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Internet <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoriga almashtirildi"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Buni istalgan vaqtda sozlamalardan o‘zgartirish mumkin"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil internet ishlamayapti"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Favqulodda chaqiruv ishlamayapti"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ovozli chaqiruvlar ishlamaydi"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ilovaga o‘zining komponentlarini xotirada doimiy saqlashga ruxsat beradi. Bu mavjud xotirani cheklashi va telefonni sekin ishlashiga sabab bo‘lishi mumkin."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"faol xizmatlarni ishga tushirish"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Ilovaga faol xizmatlardan foydalanishga ruxsat beradi."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"ilovalar egallagan xotira joyini hisoblash"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Ilova o‘zining kodi, ma’lumotlari va kesh o‘lchami to‘g‘risidagi ma’lumotlarni olishi mumkin"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"tizim sozlamalarini o‘zgartirish"</string>
@@ -449,6 +495,8 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu ilova ishlayotganida u mikrofon orqali audio yozib olishi mumkin."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"orqa fonda ovoz yozib olish"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu ilova xohlagan vaqtda mikrofon yordami audio yozib olishi mumkin."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ilova oynalarining skrinshotga olinishini aniqlash"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ilova ishlatilayotgan vaqtda skrinshot olinganda bu ilova ogohlantiriladi."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM kartaga buyruqlar yuborish"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Dasturga SIM kartaga buyruqlar jo‘natishga ruxsat beradi. Bu juda ham xavfli."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"jismoniy harakatni aniqlash"</string>
@@ -700,6 +748,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Ilovaga video fayllarni umumiy xotiradan oʻqish imkonini beradi."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"umumiy xotiradan rasmli fayllarni oʻqish"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Ilovaga rasm fayllarini umumiy xotiradan oʻqish imkonini beradi."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"umumiy xotiradan siz tanlagan rasm va video fayllarni oʻqish"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Ilovaga siz tanlagan rasm va video fayllarni umumiy xotiradan oʻqish imkonini beradi."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"umumiy xotiradagi kontentlarni tahrirlash yoki oʻchirib tashlash"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Ilovaga umumiy xotiradagi kontentlarga yozish imkonini beradi."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP qo‘ng‘iroqlarini amalga oshirish/qabul qilish"</string>
@@ -2050,12 +2100,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"O‘CHIRIB TASHLASH"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"BARIBIR OCHILSIN"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Zararli ilova aniqlandi"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ilovasining qurilmadagi barcha jurnallarga kirishiga ruxsat berilsinmi?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Bir matalik foydalanishga ruxsat berish"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Rad etish"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi.\n\nBatafsil: g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Boshqa chiqmasin"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasi <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatish uchun ruxsat so‘ramoqda"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Tahrirlash"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Chaqiruvlar va bildirishnomalar tebranadi"</string>
@@ -2296,6 +2340,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> qurilmasidan telefonning kamerasiga kirish imkonsiz"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> qurilmasidan planshetning kamerasiga kirish imkonsiz"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Bu kontent striming vaqtida ochilmaydi. Telefon orqali urininb koʻring."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Tizim standarti"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 68bb062..ed0e98a 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Số gọi đến mặc định thành không bị giới hạn. Cuộc gọi tiếp theo. Không bị giới hạn"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Dịch vụ không được cấp phép."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Bạn không thể thay đổi cài đặt ID người gọi."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Đã chuyển dữ liệu sang <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Bạn có thể thay đổi chế độ này bất cứ lúc nào trong phần Cài đặt"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Không có dịch vụ dữ liệu di động"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Không có dịch vụ gọi khẩn cấp"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Không có dịch vụ thoại"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Cho phép ứng dụng tạo sự đồng nhất cho các phần của mình trong bộ nhớ. Việc này có thể hạn chế bộ nhớ đối với các ứng dụng khác đang làm chậm điện thoại."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"chạy dịch vụ trên nền trước"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Cho phép ứng dụng sử dụng các dịch vụ trên nền trước."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"đo dung lượng lưu trữ ứng dụng"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Cho phép ứng dụng truy xuất mã, dữ liệu và kích thước bộ nhớ đệm của chính ứng dụng"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"sửa đổi các chế độ cài đặt hệ thống"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Ứng dụng này có thể ghi âm bằng micrô khi bạn đang dùng ứng dụng."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ghi âm trong nền"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ứng dụng này có thể ghi âm bằng micrô bất kỳ lúc nào."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"gửi lệnh đến SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Cho phép ứng dụng gửi lệnh đến SIM. Việc này rất nguy hiểm."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"nhận dạng hoạt động thể chất"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Cho phép ứng dụng đọc tệp video trong bộ nhớ dùng chung."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"đọc tệp hình ảnh trong bộ nhớ dùng chung"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Cho phép ứng dụng đọc tệp hình ảnh trong bộ nhớ dùng chung."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"đọc các tệp hình ảnh và video mà người dùng đã chọn trong bộ nhớ dùng chung"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Cho phép ứng dụng đọc các tệp hình ảnh và video mà bạn chọn trong bộ nhớ dùng chung."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"sửa đổi hoặc xóa nội dung của bộ nhớ dùng chung"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Cho phép ứng dụng ghi nội dung của bộ nhớ dùng chung."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"thực hiện/nhận các cuộc gọi qua SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"GỠ CÀI ĐẶT"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VẪN MỞ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Đã phát hiện ứng dụng độc hại"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Cho phép <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> truy cập vào tất cả các nhật ký thiết bị?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Cho phép truy cập một lần"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Không cho phép"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào toàn bộ nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng các nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào mọi nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn.\n\nTìm hiểu thêm tại g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Không hiện lại"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> muốn hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Chỉnh sửa"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Cuộc gọi và thông báo sẽ rung"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Không truy cập được vào máy ảnh trên điện thoại từ <xliff:g id="DEVICE">%1$s</xliff:g> của bạn"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Không truy cập được vào máy ảnh trên máy tính bảng từ <xliff:g id="DEVICE">%1$s</xliff:g> của bạn"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Bạn không thể truy cập vào nội dung này trong khi phát trực tuyến. Hãy thử trên điện thoại."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Theo chế độ mặc định của hệ thống"</string>
<string name="default_card_name" msgid="9198284935962911468">"THẺ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index f2b033e..f73900c 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"默认显示本机号码,在下一次通话中也显示"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供服务。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"您无法更改来电显示设置。"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"移动数据网络已切换至“<xliff:g id="CARRIERDISPLAY">%s</xliff:g>”"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"您可以随时在“设置”中更改这项设置"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"无法使用移动数据服务"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"无法使用紧急呼救服务"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"无法使用语音通话服务"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"允许该应用在内存中持续保留其自身的某些组件。这会限制其他应用可用的内存,从而减缓手机运行速度。"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"运行前台服务"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"允许该应用使用前台服务。"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"计算应用存储空间"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"允许应用检索其代码、数据和缓存大小"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"修改系统设置"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"当您使用此应用时,它可以使用麦克风录音。"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在后台录音"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"此应用可以随时使用麦克风录音。"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"向 SIM 卡发送命令"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"允许应用向SIM卡发送命令(此权限具有很高的危险性)。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"识别身体活动"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"允许应用读取您共享存储空间中的视频文件。"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"读取共享存储空间中的图片文件"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"允许应用读取您共享存储空间中的图片文件。"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"从共享存储空间读取用户选择的图片和视频文件"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"使应用能够从共享存储空间读取您所选的图片和视频文件。"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"修改或删除您共享存储空间中的内容"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"允许该应用写入您共享存储空间中的内容。"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"拨打/接听SIP电话"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"卸载"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"仍然打开"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"检测到有害应用"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"允许“<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>”访问所有设备日志吗?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允许访问一次"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允许"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。\n\n如需了解详情,请访问 g.co/android/devicelogs。"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不再显示"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"“<xliff:g id="APP_0">%1$s</xliff:g>”想要显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"编辑"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"有来电和通知时会振动"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"无法从<xliff:g id="DEVICE">%1$s</xliff:g>上访问手机的摄像头"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"无法从<xliff:g id="DEVICE">%1$s</xliff:g>上访问平板电脑的摄像头"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"流式传输时无法访问此内容。您可以尝试在手机上访问。"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"系统默认设置"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 63995bb..072ce46 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示來電號碼,下一通電話也繼續顯示。"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供此服務。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"您無法更改來電顯示設定。"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"流動數據已切換至「<xliff:g id="CARRIERDISPLAY">%s</xliff:g>」"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"您隨時可在「設定」中變更此設定"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"無法使用流動數據服務"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"無法撥打緊急電話"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"沒有語音服務"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"允許應用程式設定本身的某些部分持續佔用記憶體。這樣可能會限制其他應用程式可用的記憶體,並拖慢手機的運作速度。"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"執行前景服務"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"允許應用程式使用前景服務。"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"允許應用程式擷取本身的程式碼、資料和快取大小"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"修改系統設定"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"此應用程式在使用期間可使用麥克風錄音。"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在背景錄音"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"此應用程式可隨時使用麥克風錄音。"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"發送指令至 SIM 卡"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這項操作具有高危險性。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"識別體能活動"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"允許應用程式讀取共用儲存空間中的影片檔案。"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"讀取共用儲存空間中的圖片檔案"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"允許應用程式讀取共用儲存空間中的圖片檔案。"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"讀取使用者在共用儲存空間中選取的圖片和影片檔案"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"允許應用程式讀取您在共用儲存空間中選取的圖片和影片檔案。"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"修改或刪除您共用儲存空間的內容"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"允許應用程式寫入您共用儲存空間的內容。"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"撥打/接聽 SIP 電話"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"解除安裝"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"仍要開啟"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"偵測到有害的應用程式"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許存取一次"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"裝置記錄會記下裝置上的活動。應用程式可透過這些記錄找出並修正問題。\n\n部分記錄可能包含敏感資料,因此請只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄,且裝置製造商可能仍可存取裝置上的部分記錄或資料。"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"裝置記錄會記下裝置上的活動。應用程式可使用這些記錄找出並修正問題。\n\n有些記錄可能包含敏感資料,因此建議只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄。您的裝置製造商可能仍可存取裝置上的一些記錄或資料。\n\n詳情請瀏覽 g.co/android/devicelogs。"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"有來電和通知時會震動"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取手機的相機"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取平板電腦的相機"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"串流播放時無法使用,請改用手機。"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"系統預設"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 55f9321..f1b0f23 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示本機號碼,下一通電話也繼續顯示。"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"無法提供此服務。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"你無法變更來電顯示設定。"</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"行動數據已切換至「<xliff:g id="CARRIERDISPLAY">%s</xliff:g>」"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"你隨時可以前往「設定」變更這項設定"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"沒有行動數據傳輸服務"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"無法撥打緊急電話"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"無法使用語音通話服務"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"允許應用程式讓部分內容佔用記憶體,持續執行。這項設定可能會限制其他應用程式可用的記憶體,並拖慢手機運作速度。"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"執行前景服務"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"允許應用程式使用前景服務。"</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"允許應用程式擷取本身的程式碼、資料及快取大小"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"修改系統設定"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"這個應用程式在使用期間可以使用麥克風錄音。"</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在背景錄音"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"這個應用程式隨時可以使用麥克風錄音。"</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"傳送指令到 SIM 卡"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這麼做非常危險。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"辨識體能活動"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"允許應用程式讀取共用儲存空間中的影片檔案。"</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"讀取共用儲存空間中的圖片檔案"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"允許應用程式讀取共用儲存空間中的圖片檔案。"</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"讀取使用者在共用儲存空間中選取的圖片和影片檔案"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"允許應用程式讀取你在共用儲存空間中選取的圖片和影片檔案。"</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"修改或刪除共用儲存空間中的內容"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"允許這個應用程式寫入共用儲存空間中的內容。"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"撥打/接聽 SIP 通話"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"解除安裝"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"仍要開啟"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"偵測到有害應用程式"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許一次性存取"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"系統會透過裝置記錄記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n某些記錄可能含有機密資訊,因此請勿讓不信任的應用程式存取所有裝置記錄。\n\n即使你不允許這個應用程式存取所有裝置記錄,這個應用程式仍能存取自己的記錄,而且裝置製造商或許仍可存取裝置的某些記錄或資訊。"</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"裝置記錄會記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n由於某些記錄可能含有機密資訊,建議只讓信任的應用程式存取所有裝置記錄。\n\n如果你不允許這個應用程式存取所有裝置記錄,這個應用程式仍可存取屬於自己的記錄,而裝置製造商也或許還是可以存取裝置的某些記錄或資訊。\n\n請參閱以下網址瞭解詳情:g.co/android/devicelogs。"</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想要顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"有來電和通知時會震動"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取手機的相機"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取平板電腦的相機"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"串流播放時無法存取這項內容,請改用手機。"</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"系統預設"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 2218b76..c0a2874 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -72,10 +72,8 @@
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"I-ID Yomshayeli ishintshela kokungavinjelwe. Ucingo olulandelayo: Aluvinjelwe"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Isevisi ayilungiselelwe."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ngeke ukwazi ukuguqul izilungiselelo zemininingwane yoshayayo."</string>
- <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
- <skip />
- <!-- no translation found for auto_data_switch_content (803557715007110959) -->
- <skip />
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Ushintshele idatha ku-<xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Ungashintsha lokhu noma nini kumasethingi"</string>
<string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ayikho isevisi yedatha yeselula"</string>
<string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ukushaya okuphuthumayo akutholakali"</string>
<string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ayikho isevisi yezwi"</string>
@@ -397,6 +395,54 @@
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ivumela uhlelo kusebenza ukwenza izingxenye yazo ezicindezelayo kumemori. Lokhu kungakhawulela imemori ekhona kwezinye izinhlelo zokusebenza ukwenza ukuthi ifoni ingasheshi."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"qalisa amasevisi waphambili"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Vumela uhlelo lokusebenza ukusebenzisa amasevisi wangaphambili."</string>
+ <!-- no translation found for permlab_foregroundServiceCamera (7814751737955715297) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceCamera (6973701931250595727) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceConnectedDevice (3019650546176872501) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceConnectedDevice (1067457315741352963) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceDataSync (5847463514326881076) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceDataSync (2267140263423973050) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceLocation (3745428302378535690) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceLocation (118894034365177183) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaPlayback (4002687983891935514) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaPlayback (3638032446063968043) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMediaProjection (2630868915733312527) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMediaProjection (4805677128082002298) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceMicrophone (7390033424890545399) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceMicrophone (1206041516173483201) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServicePhoneCall (627937743867697892) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServicePhoneCall (5941660252587015147) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceHealth (3675776442080928184) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceHealth (2024586220562667185) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceRemoteMessaging (105670277002780950) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceRemoteMessaging (8767598075877576277) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSystemExempted (1597663713590612685) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSystemExempted (947381760834649622) -->
+ <skip />
+ <!-- no translation found for permlab_foregroundServiceSpecialUse (7973536745876645082) -->
+ <skip />
+ <!-- no translation found for permdesc_foregroundServiceSpecialUse (646713654541885919) -->
+ <skip />
<string name="permlab_getPackageSize" msgid="375391550792886641">"linganisa isikhala sokugcina uhlelo lokusebenza"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"Ivuela uhlelo lokusebenza ukuthi ithole kabusha ikhodi yayo, i-dat kanye nosayizi abagcinwe okwesikhashana."</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"shintsha amasethingi esistimu"</string>
@@ -449,6 +495,10 @@
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Lolu hlelo lokusebenza lungarekhoda umsindo lisebenzisa imakrofoni kuyilapho uhlelo lokusebenza lusetshenziswa."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rekhoda umsindo ngemuva"</string>
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Lolu hlelo lokusebenza lungafunda umsindo lisebenzisa imakrofoni noma kunini."</string>
+ <!-- no translation found for permlab_detectScreenCapture (4447042362828799433) -->
+ <skip />
+ <!-- no translation found for permdesc_detectScreenCapture (3485784917960342284) -->
+ <skip />
<string name="permlab_sim_communication" msgid="176788115994050692">"thumela imilayezo ku-SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Ivumela uhlelo lokusebenza ukuthumela imiyalo ku-SIM. Lokhu kuyingozi kakhulu."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"bona umsebenzi"</string>
@@ -700,6 +750,8 @@
<string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Ivumela i-app ukuthi ifunde amafayela amavidiyo kwisitoreji sakho owabelane ngaso."</string>
<string name="permlab_readMediaImages" msgid="4057590631020986789">"funda amafayela womfanekiso wesitoreji okwabelenwe ngaso"</string>
<string name="permdesc_readMediaImages" msgid="5836219373138469259">"Ivumela i-app ukuthi ifunde amafayela ezithombe kwisitoreji sakho owabelane ngaso."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"funda amafayela akhethiwe wesithombe namavidiyo akhethiwe kusitoreji esabiwe"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Ivumela i-app ukuthi ifunde amafayela wesithombe namavidiyo owakhethayo kusitoreji esabiwe."</string>
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"guqula noma susa okuqukethwe kwesitoreji sakho esabiwe"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Ivumela uhlelo lokusebenza ukuthi lubhale okuqukethwe kwesitoreji sakho esabiwe."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"yenza/thola amakholi we-SIP"</string>
@@ -2050,12 +2102,6 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"KHIPHA"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VULA NOMA KUNJALO"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Uhlelo lokusebenza oluyingozi lutholakele"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vumela i-<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ukuba ifinyelele wonke amalogu edivayisi?"</string>
- <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Vumela ukufinyelela kwesikhathi esisodwa"</string>
- <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ungavumeli"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho.\n\nFunda kabanzi ku-g.co/android/devicelogs."</string>
- <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ungabonisi futhi"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"I-<xliff:g id="APP_0">%1$s</xliff:g> ifuna ukubonisa izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Hlela"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Amakholi nezaziso zizodlidliza"</string>
@@ -2296,6 +2342,8 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ayikwazi ukufinyelela ikhamera yefoni kusuka ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ayikwazi ukufinyelela ikhamera yethebulethi kusuka ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Lokhu akukwazi ukufinyelelwa ngenkathi usakaza. Zama efonini yakho kunalokho."</string>
+ <!-- no translation found for vdm_pip_blocked (4036107522497281397) -->
+ <skip />
<string name="system_locale_title" msgid="711882686834677268">"Okuzenzakalelayo kwesistimu"</string>
<string name="default_card_name" msgid="9198284935962911468">"IKHADI <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index eac2b94..a5c0827 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1586,19 +1586,71 @@
together. -->
<attr name="foregroundServiceType">
<!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch,
- transfer over network between device and cloud. -->
+ transfer over network between device and cloud.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, this type should NOT
+ be used: calling
+ {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+ this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ is still allowed, but calling it with this type on devices running future platform
+ releases may get a {@link android.app.ForegroundServiceTypeNotAllowedException}.
+ -->
<flag name="dataSync" value="0x01" />
- <!-- Music, video, news or other media play. -->
+ <!-- Music, video, news or other media play.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
+ -->
<flag name="mediaPlayback" value="0x02" />
<!-- Ongoing operations related to phone calls, video conferencing,
- or similar interactive communication. -->
+ or similar interactive communication.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+ {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+ -->
<flag name="phoneCall" value="0x04" />
- <!-- GPS, map, navigation location update. -->
+ <!-- GPS, map, navigation location update.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+ following permissions:
+ {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+ {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+ -->
<flag name="location" value="0x08" />
- <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction. -->
+ <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+ following permissions:
+ {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+ {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+ {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+ {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+ {@link android.Manifest.permission#NFC},
+ {@link android.Manifest.permission#TRANSMIT_IR},
+ or has been granted the access to one of the attached USB devices/accessories.
+ -->
<flag name="connectedDevice" value="0x10" />
<!-- Managing a media projection session, e.g, for screen recording or taking
- screenshots.-->
+ screenshots.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user
+ must have allowed the screen capture request from this app.
+ -->
<flag name="mediaProjection" value="0x20" />
<!-- Use the camera device or record video.
@@ -1606,6 +1658,12 @@
and above, a foreground service will not be able to access the camera if this type is
not specified in the manifest and in
{@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+ {@link android.Manifest.permission#CAMERA}.
-->
<flag name="camera" value="0x40" />
<!--Use the microphone device or record audio.
@@ -1614,8 +1672,54 @@
and above, a foreground service will not be able to access the microphone if this type
is not specified in the manifest and in
{@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the
+ following permissions:
+ {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+ {@link android.Manifest.permission#RECORD_AUDIO}.
-->
<flag name="microphone" value="0x80" />
+ <!--Health, wellness and fitness.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+ permissions
+ {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+ {@link android.Manifest.permission#BODY_SENSORS},
+ {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+ -->
+ <flag name="health" value="0x100" />
+ <!-- Messaging use cases which host local server to relay messages across devices.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_REMOTE_MESSAGING} in order to use
+ this type.
+ -->
+ <flag name="remoteMessaging" value="0x200" />
+ <!-- The system exmpted foreground service use cases.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_SYSTEM_EXEMPTED} in order to use
+ this type. Apps are allowed to use this type only in the use cases listed in
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+ -->
+ <flag name="systemExempted" value="0x400" />
+ <!-- "Short service" foreground service type. See
+ TODO: Change it to a real link
+ {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ for more details.
+ -->
+ <flag name="shortService" value="0x800" />
+ <!-- Use cases that can't be categorized into any other foreground service types, but also
+ can't use @link android.app.job.JobInfo.Builder} APIs.
+ See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
+ best practice of the use of this type.
+
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_SPECIAL_USE} in order to use
+ this type.
+ -->
+ <flag name="specialUse" value="0x40000000" />
</attr>
<!-- Enable sampled memory bug detection in this process.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3d245e9..6c18259 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -342,22 +342,6 @@
<!-- Mask to use when checking skb mark defined in config_networkWakeupPacketMark above. -->
<integer name="config_networkWakeupPacketMask">0</integer>
- <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
- Those frames are identified by the field Eth-type having values
- less than 0x600 -->
- <bool translatable="false" name="config_apfDrop802_3Frames">true</bool>
-
- <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
- will be dropped
- TODO: need to put proper values, these are for testing purposes only -->
- <integer-array translatable="false" name="config_apfEthTypeBlackList">
- <item>0x88A2</item>
- <item>0x88A4</item>
- <item>0x88B8</item>
- <item>0x88CD</item>
- <item>0x88E3</item>
- </integer-array>
-
<!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual
device behaviour is controlled by Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE.
This is the default value of that setting. -->
@@ -1871,6 +1855,10 @@
-->
<string name="config_defaultCaptivePortalLoginPackageName" translatable="false">com.android.captiveportallogin</string>
+ <!-- The package name of the dock manager app. Must be granted the
+ POST_NOTIFICATIONS permission. -->
+ <string name="config_defaultDockManagerPackageName" translatable="false"></string>
+
<!-- Whether to enable geocoder overlay which allows geocoder to be replaced
by an app at run-time. When disabled, only the
config_geocoderProviderPackageName package will be searched for
@@ -2438,15 +2426,15 @@
<integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
<!-- Limit of how long the device can remain unlocked due to attention checking. -->
<integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. -->
- <!-- Is the system user the only user allowed to dream. -->
- <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+ <!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. -->
+ <bool name="config_dreamsOnlyEnabledForDockUser">false</bool>
<!-- Whether dreams are disabled when ambient mode is suppressed. -->
<bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
<!-- The duration in milliseconds of the dream opening animation. -->
<integer name="config_dreamOpenAnimationDuration">250</integer>
<!-- The duration in milliseconds of the dream closing animation. -->
- <integer name="config_dreamCloseAnimationDuration">100</integer>
+ <integer name="config_dreamCloseAnimationDuration">300</integer>
<!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
@@ -2688,9 +2676,9 @@
Should be false for most devices, except automotive vehicle with passenger displays. -->
<bool name="config_multiuserUsersOnSecondaryDisplays">false</bool>
- <!-- Whether to automatically switch a non-primary user back to the primary user after a
- timeout when the device is docked. -->
- <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+ <!-- Whether to automatically switch to the designated Dock User (the user chosen for
+ displaying dreams, etc.) after a timeout when the device is docked. -->
+ <bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
<!-- Whether to only install system packages on a user if they're allowlisted for that user
type. These are flags and can be freely combined.
@@ -2901,7 +2889,7 @@
>com.android.systemui/com.android.systemui.usb.UsbDebuggingActivity</string>
<!-- Name of the activity that prompts the secondary user to acknowledge they need to
- switch to the primary user to enable USB debugging.
+ switch to an admin user to enable USB debugging.
Can be customized for other product types -->
<string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
@@ -2913,7 +2901,7 @@
>com.android.systemui/com.android.systemui.wifi.WifiDebuggingActivity</string>
<!-- Name of the activity that prompts the secondary user to acknowledge they need to
- switch to the primary user to enable wireless debugging.
+ switch to an admin user to enable wireless debugging.
Can be customized for other product types -->
<string name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string>
@@ -2974,6 +2962,10 @@
<string name="config_carrierAppInstallDialogComponent" translatable="false"
>com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
+ <!-- Name of the dialog that is used to get or save an app credential -->
+ <string name="config_credentialManagerDialogComponent" translatable="false"
+ >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
+
<!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
<string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
@@ -3696,6 +3688,12 @@
experience while the device is non-interactive. -->
<bool name="config_emergencyGestureEnabled">true</bool>
+ <!-- Default value for Use Emergency SOS in Settings false = disabled, true = enabled -->
+ <bool name="config_defaultEmergencyGestureEnabled">true</bool>
+
+ <!-- Default value for Use Play countdown alarm in Settings false = disabled, true = enabled -->
+ <bool name="config_defaultEmergencyGestureSoundEnabled">false</bool>
+
<!-- Allow the gesture power + volume up to change the ringer mode while the device
is interactive. -->
<bool name="config_volumeHushGestureEnabled">true</bool>
@@ -3980,7 +3978,7 @@
<!-- Colon separated list of package names that should be granted DND access -->
<string name="config_defaultDndAccessPackages" translatable="false">com.android.camera2</string>
- <!-- User restrictions set when the first user is created.
+ <!-- User restrictions set on the SYSTEM user when it is first created.
Note: Also update appropriate overlay files. -->
<string-array translatable="false" name="config_defaultFirstUserRestrictions">
</string-array>
@@ -4422,6 +4420,10 @@
or empty if the default should be used. -->
<string translatable="false" name="config_deviceSpecificDeviceStatePolicyProvider"></string>
+ <!-- Class name of the device specific implementation of InputMethodManagerService
+ or empty if the default should be used. -->
+ <string translatable="false" name="config_deviceSpecificInputMethodManagerService"></string>
+
<!-- Component name of media projection permission dialog -->
<string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
@@ -5951,4 +5953,9 @@
<!-- Whether the lock screen is allowed to run its own live wallpaper,
different from the home screen wallpaper. -->
<bool name="config_independentLockscreenLiveWallpaper">false</bool>
+
+ <!-- List of certificate to be used for font fs-verity integrity verification -->
+ <string-array translatable="false" name="config_fontManagerServiceCerts">
+ </string-array>
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1f459c6..7714082 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1143,6 +1143,66 @@
<string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceCamera">run foreground service with the type \"camera\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceCamera">Allows the app to make use of foreground services with the type \"camera\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceConnectedDevice">run foreground service with the type \"connectedDevice\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceConnectedDevice">Allows the app to make use of foreground services with the type \"connectedDevice\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceDataSync">run foreground service with the type \"dataSync\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceDataSync">Allows the app to make use of foreground services with the type \"dataSync\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceLocation">run foreground service with the type \"location\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceLocation">Allows the app to make use of foreground services with the type \"location\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMediaPlayback">run foreground service with the type \"mediaPlayback\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMediaPlayback">Allows the app to make use of foreground services with the type \"mediaPlayback\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMediaProjection">run foreground service with the type \"mediaProjection\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMediaProjection">Allows the app to make use of foreground services with the type \"mediaProjection\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMicrophone">run foreground service with the type \"microphone\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMicrophone">Allows the app to make use of foreground services with the type \"microphone\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServicePhoneCall">run foreground service with the type \"phoneCall\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServicePhoneCall">Allows the app to make use of foreground services with the type \"phoneCall\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceHealth">run foreground service with the type \"health\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceHealth">Allows the app to make use of foreground services with the type \"health\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceRemoteMessaging">run foreground service with the type \"remoteMessaging\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceRemoteMessaging">Allows the app to make use of foreground services with the type \"remoteMessaging\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceSystemExempted">run foreground service with the type \"systemExempted\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceSystemExempted">Allows the app to make use of foreground services with the type \"systemExempted\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_getPackageSize">measure app storage space</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
@@ -1302,6 +1362,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
<string name="permdesc_recordBackgroundAudio">This app can record audio using the microphone at any time.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_detectScreenCapture">detect screen captures of app windows</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_detectScreenCapture">This app will get notified when a screenshot is taken while the app is in use.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_sim_communication">send commands to the SIM</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -5759,32 +5824,6 @@
<!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] -->
<string name="harmful_app_warning_title">Harmful app detected</string>
- <!-- Title for the log access confirmation dialog. [CHAR LIMIT=NONE] -->
- <string name="log_access_confirmation_title">Allow <xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> to access all device logs?</string>
- <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=40] -->
- <string name="log_access_confirmation_allow">Allow one-time access</string>
- <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
- <string name="log_access_confirmation_deny">Don\u2019t allow</string>
-
- <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
- <string name="log_access_confirmation_body" product="default">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
- \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
- </string>
-
- <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
- <string name="log_access_confirmation_body" product="tv">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
- \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
- </string>
-
- <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more" product="default" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></string>
-
- <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more" product="tv" translatable="false"></string>
-
- <!-- Privacy notice do not show [CHAR LIMIT=20] -->
- <string name="log_access_do_not_show_again">Don\u2019t show again</string>
-
<!-- Text describing a permission request for one app to show another app's
slices [CHAR LIMIT=NONE] -->
<string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
@@ -6352,6 +6391,8 @@
<string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
<!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] -->
<string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string>
+ <!-- Error message indicating the user cannot view picture-in-picture when running on a virtual device. [CHAR LIMIT=NONE] -->
+ <string name="vdm_pip_blocked">Can’t view picture-in-picture while streaming</string>
<!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
<string name="system_locale_title">System default</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 2dd563d..476c18e 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1541,40 +1541,4 @@
<style name="NotificationTombstoneAction" parent="NotificationAction">
<item name="textColor">#555555</item>
</style>
-
- <!-- The style for log access consent text -->
- <style name="AllowLogAccess">
- <item name="android:textSize">24sp</item>
- <item name="android:fontFamily">google-sans</item>
- </style>
-
- <style name="PrimaryAllowLogAccess">
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">google-sans-text</item>
- </style>
-
- <style name="PermissionGrantButtonTextAppearance">
- <item name="android:fontFamily">google-sans-medium</item>
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">@android:color/system_neutral1_900</item>
- </style>
-
- <style name="PermissionGrantButtonTop"
- parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
- <item name="android:layout_width">332dp</item>
- <item name="android:layout_height">56dp</item>
- <item name="android:layout_marginTop">2dp</item>
- <item name="android:layout_marginBottom">2dp</item>
- <item name="android:background">@drawable/grant_permissions_buttons_top</item>
- </style>
-
- <style name="PermissionGrantButtonBottom"
- parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
- <item name="android:layout_width">332dp</item>
- <item name="android:layout_height">56dp</item>
- <item name="android:layout_marginTop">2dp</item>
- <item name="android:layout_marginBottom">2dp</item>
- <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
- </style>
-
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f2bbbc4..5811ed9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -466,7 +466,7 @@
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="bool" name="config_multiuserUsersOnSecondaryDisplays" />
- <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
+ <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="xml" name="config_user_types" />
<java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -483,6 +483,7 @@
<java-symbol type="array" name="config_deviceSpecificSystemServices" />
<java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
<java-symbol type="string" name="config_deviceSpecificAudioService" />
+ <java-symbol type="string" name="config_deviceSpecificInputMethodManagerService" />
<java-symbol type="integer" name="config_num_physical_slots" />
<java-symbol type="integer" name="config_default_cellular_usage_setting" />
<java-symbol type="array" name="config_supported_cellular_usage_settings" />
@@ -1690,15 +1691,6 @@
<!-- From android.policy -->
<java-symbol type="anim" name="app_starting_exit" />
- <java-symbol type="anim" name="dock_top_enter" />
- <java-symbol type="anim" name="dock_top_exit" />
- <java-symbol type="anim" name="dock_bottom_enter" />
- <java-symbol type="anim" name="dock_bottom_exit" />
- <java-symbol type="anim" name="dock_bottom_exit_keyguard" />
- <java-symbol type="anim" name="dock_left_enter" />
- <java-symbol type="anim" name="dock_left_exit" />
- <java-symbol type="anim" name="dock_right_enter" />
- <java-symbol type="anim" name="dock_right_exit" />
<java-symbol type="anim" name="fade_in" />
<java-symbol type="anim" name="fade_out" />
<java-symbol type="anim" name="voice_activity_close_exit" />
@@ -2045,8 +2037,6 @@
<java-symbol type="integer" name="config_networkAvoidBadWifi" />
<java-symbol type="integer" name="config_networkWakeupPacketMark" />
<java-symbol type="integer" name="config_networkWakeupPacketMask" />
- <java-symbol type="bool" name="config_apfDrop802_3Frames" />
- <java-symbol type="array" name="config_apfEthTypeBlackList" />
<java-symbol type="integer" name="config_networkDefaultDailyMultipathQuotaBytes" />
<java-symbol type="integer" name="config_networkMeteredMultipathPreference" />
<java-symbol type="array" name="config_networkSupportedKeepaliveCount" />
@@ -2237,7 +2227,7 @@
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
<java-symbol type="string" name="config_dreamsDefaultComponent" />
<java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
- <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+ <java-symbol type="bool" name="config_dreamsOnlyEnabledForDockUser" />
<java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
<java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
<java-symbol type="array" name="config_supportedDreamComplications" />
@@ -2265,6 +2255,7 @@
<java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
+ <java-symbol type="string" name="config_credentialManagerDialogComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
<java-symbol type="string" name="config_deviceConfiguratorPackageName" />
@@ -2309,6 +2300,7 @@
<java-symbol type="id" name="media_actions" />
<java-symbol type="dimen" name="config_mediaMetadataBitmapMaxSize" />
+ <java-symbol type="array" name="config_fontManagerServiceCerts" />
<!-- From SystemUI -->
<java-symbol type="anim" name="push_down_in" />
@@ -3023,6 +3015,8 @@
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
<java-symbol type="bool" name="config_emergencyGestureEnabled" />
+ <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
+ <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
<java-symbol type="bool" name="config_volumeHushGestureEnabled" />
<java-symbol type="drawable" name="platlogo_m" />
@@ -3446,7 +3440,7 @@
<java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" />
<java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
- <!-- Default first user restrictions -->
+ <!-- Default user restrictions for the SYSTEM user -->
<java-symbol type="array" name="config_defaultFirstUserRestrictions" />
<java-symbol type="bool" name="config_permissionsIndividuallyControlled" />
@@ -3479,6 +3473,9 @@
<!-- Captive Portal Login -->
<java-symbol type="string" name="config_defaultCaptivePortalLoginPackageName" />
+ <!-- Dock Manager -->
+ <java-symbol type="string" name="config_defaultDockManagerPackageName" />
+
<!-- Optional IPsec algorithms -->
<java-symbol type="array" name="config_optionalIpSecAlgorithms" />
@@ -3939,17 +3936,6 @@
<java-symbol type="string" name="harmful_app_warning_title" />
<java-symbol type="layout" name="harmful_app_warning_dialog" />
- <java-symbol type="string" name="log_access_confirmation_allow" />
- <java-symbol type="string" name="log_access_confirmation_deny" />
- <java-symbol type="string" name="log_access_confirmation_title" />
- <java-symbol type="string" name="log_access_confirmation_body" />
- <java-symbol type="string" name="log_access_confirmation_learn_more" />
- <java-symbol type="layout" name="log_access_user_consent_dialog_permission" />
- <java-symbol type="id" name="log_access_dialog_title" />
- <java-symbol type="id" name="log_access_dialog_body" />
- <java-symbol type="id" name="log_access_dialog_allow_button" />
- <java-symbol type="id" name="log_access_dialog_deny_button" />
-
<java-symbol type="string" name="config_defaultAssistantAccessComponent" />
<java-symbol type="string" name="slices_permission_request" />
@@ -4868,6 +4854,7 @@
<!-- For VirtualDeviceManager -->
<java-symbol type="string" name="vdm_camera_access_denied" />
<java-symbol type="string" name="vdm_secure_window" />
+ <java-symbol type="string" name="vdm_pip_blocked" />
<java-symbol type="color" name="camera_privacy_light_day"/>
<java-symbol type="color" name="camera_privacy_light_night"/>
diff --git a/core/res/res/xml/config_user_types.xml b/core/res/res/xml/config_user_types.xml
index 7663150..df6b7b2 100644
--- a/core/res/res/xml/config_user_types.xml
+++ b/core/res/res/xml/config_user_types.xml
@@ -83,6 +83,8 @@
For profile and full users:
default-restrictions (with values defined in UserRestrictionUtils.USER_RESTRICTIONS)
enabled
+ user-properties
+ max-allowed
For profile users only:
max-allowed-per-parent
icon-badge
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 7cb64c8..436f058 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -48,7 +48,7 @@
],
// mockito-target-inline dependency
jni_libs: [
- "libcarservicejni",
"libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
],
}
diff --git a/core/tests/BroadcastRadioTests/AndroidTest.xml b/core/tests/BroadcastRadioTests/AndroidTest.xml
new file mode 100644
index 0000000..ed88537
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Broadcast Radio Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="BroadcastRadioTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="BroadcastRadioTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.hardware.radio.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java
new file mode 100644
index 0000000..c6021ec
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java
@@ -0,0 +1,78 @@
+/*
+ * 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.server.broadcastradio;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import android.util.Log;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Base class to make it easier to write tests that uses {@code ExtendedMockito} for radio.
+ *
+ */
+public abstract class ExtendedRadioMockitoTestCase {
+
+ private static final String TAG = "RadioMockitoTestCase";
+
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private MockitoSession mSession;
+
+ @Before
+ public void startSession() {
+ StaticMockitoSessionBuilder builder = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT);
+ initializeSession(builder);
+ mSession = builder.startMocking();
+ }
+
+ /**
+ * Initializes the mockito session for radio test.
+ *
+ * <p>Typically used to define which classes should have static methods mocked or spied.
+ */
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ if (DEBUG) {
+ Log.d(TAG, "initializeSession()");
+ }
+ }
+
+ @After
+ public final void finishSession() {
+ if (mSession == null) {
+ Log.w(TAG, "finishSession(): no session");
+ return;
+ }
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "finishSession()");
+ }
+ } finally {
+ // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
+ // statements), otherwise it would cause failures on future tests as mockito
+ // cannot start a session when a previous one is not finished
+ mSession.finishMocking();
+ }
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
index 7f4ea11..a2df426 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -16,11 +16,13 @@
package com.android.server.broadcastradio;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,30 +33,36 @@
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-import java.util.Arrays;
+import java.util.List;
/**
* Tests for {@link android.hardware.radio.IRadioService} with AIDL HAL implementation
*/
-@RunWith(MockitoJUnitRunner.class)
-public final class IRadioServiceAidlImplTest {
+public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCase {
private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+ private static final String AM_FM_SERVICE_NAME =
+ "android.hardware.broadcastradio.IBroadcastRadio/amfm";
+ private static final String DAB_SERVICE_NAME =
+ "android.hardware.broadcastradio.IBroadcastRadio/dab";
private IRadioServiceAidlImpl mAidlImpl;
@Mock
private BroadcastRadioService mServiceMock;
@Mock
+ private IBinder mServiceBinderMock;
+ @Mock
private BroadcastRadioServiceImpl mHalMock;
@Mock
private RadioManager.ModuleProperties mModuleMock;
@@ -73,7 +81,7 @@
public void setUp() throws Exception {
doNothing().when(mServiceMock).enforcePolicyAccess();
- when(mHalMock.listModules()).thenReturn(Arrays.asList(mModuleMock));
+ when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
.thenReturn(mTunerMock);
when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
@@ -81,11 +89,26 @@
mAidlImpl = new IRadioServiceAidlImpl(mServiceMock, mHalMock);
}
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(ServiceManager.class);
+ }
+
+ @Test
+ public void getServicesNames_forAidlImpl() {
+ doReturn(null).when(() -> ServiceManager.waitForDeclaredService(
+ AM_FM_SERVICE_NAME));
+ doReturn(mServiceBinderMock).when(() -> ServiceManager.waitForDeclaredService(
+ DAB_SERVICE_NAME));
+
+ assertWithMessage("Names of services available")
+ .that(IRadioServiceAidlImpl.getServicesNames()).containsExactly(DAB_SERVICE_NAME);
+ }
+
@Test
public void loadModules_forAidlImpl() {
assertWithMessage("Modules loaded in AIDL HAL")
- .that(mAidlImpl.listModules())
- .containsExactly(mModuleMock);
+ .that(mAidlImpl.listModules()).containsExactly(mModuleMock);
}
@Test
@@ -105,5 +128,4 @@
assertWithMessage("Close handle of announcement listener for HAL 2")
.that(closeHandle).isEqualTo(mICloseHandle);
}
-
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
index f28e27d..5ab9435 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
@@ -18,9 +18,9 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index 2cb058b..a421218 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -31,6 +31,16 @@
throw new UnsupportedOperationException("AidlTestUtils class is noninstantiable");
}
+ static RadioManager.ModuleProperties makeDefaultModuleProperties() {
+ return new RadioManager.ModuleProperties(
+ /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+ /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+ /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+ /* isCaptureSupported= */ false, /* bands= */ null,
+ /* isBgScanSupported= */ false, new int[] {}, new int[] {},
+ /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+ }
+
static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
return new RadioManager.ProgramInfo(selector,
selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
index 699212a..9a1af19 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -57,11 +56,8 @@
private IAnnouncementListener mListenerMock;
@Mock
private IBinder mBinderMock;
- // Array of mocked radio modules
private RadioModule[] mRadioModuleMocks;
- // Array of mocked close handles
private ICloseHandle[] mCloseHandleMocks;
- // Array of mocked announcements
private Announcement[] mAnnouncementMocks;
@Before
@@ -72,7 +68,7 @@
mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
- verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), anyInt());
+ verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), eq(0));
mDeathRecipient = deathRecipientCaptor.getValue();
}
@@ -105,7 +101,8 @@
moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
- assertWithMessage("Number of announcements %s", announcementsCaptor.getValue())
+ assertWithMessage("Number of announcements %s after %s announcements were updated",
+ announcementsCaptor.getValue(), index + 1)
.that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
}
}
@@ -117,7 +114,7 @@
mAnnouncementAggregator.close();
verify(mCloseHandleMocks[0]).close();
- verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+ verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
}
@Test
@@ -140,7 +137,7 @@
mAnnouncementAggregator.close();
verify(mCloseHandleMocks[0]).close();
- verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+ verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
}
@Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
new file mode 100644
index 0000000..635d1e7
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.server.broadcastradio.aidl;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.os.IBinder;
+import android.os.IServiceCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
+
+ private static final int FM_RADIO_MODULE_ID = 0;
+ private static final int DAB_RADIO_MODULE_ID = 1;
+ private static final ArrayList<String> SERVICE_LIST =
+ new ArrayList<>(Arrays.asList("FmService", "DabService"));
+
+ private BroadcastRadioServiceImpl mBroadcastRadioService;
+ private IBinder.DeathRecipient mFmDeathRecipient;
+
+ @Mock
+ private RadioManager.ModuleProperties mFmModuleMock;
+ @Mock
+ private RadioManager.ModuleProperties mDabModuleMock;
+ @Mock
+ private RadioModule mFmRadioModuleMock;
+ @Mock
+ private RadioModule mDabRadioModuleMock;
+ @Mock
+ private IBroadcastRadio mFmHalServiceMock;
+ @Mock
+ private IBroadcastRadio mDabHalServiceMock;
+ @Mock
+ private IBinder mFmBinderMock;
+ @Mock
+ private IBinder mDabBinderMock;
+ @Mock
+ private TunerSession mFmTunerSessionMock;
+ @Mock
+ private ITunerCallback mTunerCallbackMock;
+
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(ServiceManager.class)
+ .spyStatic(RadioModule.class);
+ }
+
+ @Test
+ public void listModules_withMultipleServiceNames() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("Radio modules in AIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.listModules())
+ .containsExactly(mFmModuleMock, mDabModuleMock);
+ }
+
+ @Test
+ public void hasModules_withIdFoundInModules() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("DAB radio module in AIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID)).isTrue();
+ }
+
+ @Test
+ public void hasModules_withIdNotFoundInModules() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("Radio module of id not found in AIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID + 1)).isFalse();
+ }
+
+ @Test
+ public void hasAnyModules_withModulesExist() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("Any radio module in AIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.hasAnyModules()).isTrue();
+ }
+
+ @Test
+ public void openSession_withIdFound() throws Exception {
+ createBroadcastRadioService();
+
+ ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+
+ assertWithMessage("Session opened in FM radio module")
+ .that(session).isEqualTo(mFmTunerSessionMock);
+ }
+
+ @Test
+ public void openSession_withIdNotFound() throws Exception {
+ createBroadcastRadioService();
+
+ ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+
+ assertWithMessage("Session opened with id not found").that(session).isNull();
+ }
+
+ @Test
+ public void binderDied_forDeathRecipient() throws Exception {
+ createBroadcastRadioService();
+
+ mFmDeathRecipient.binderDied();
+
+ verify(mFmRadioModuleMock).closeSessions(eq(RadioTuner.ERROR_HARDWARE_FAILURE));
+ assertWithMessage("FM radio module after FM broadcast radio HAL service died")
+ .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isFalse();
+ }
+
+ private void createBroadcastRadioService() throws RemoteException {
+ mockServiceManager();
+ mBroadcastRadioService = new BroadcastRadioServiceImpl(SERVICE_LIST);
+ }
+
+ private void mockServiceManager() throws RemoteException {
+ doAnswer((Answer<Void>) invocation -> {
+ String serviceName = (String) invocation.getArguments()[0];
+ IServiceCallback serviceCallback = (IServiceCallback) invocation.getArguments()[1];
+ IBinder mockBinder = serviceName.equals("FmService") ? mFmBinderMock : mDabBinderMock;
+ serviceCallback.onRegistration(serviceName, mockBinder);
+ return null;
+ }).when(() -> ServiceManager.registerForNotifications(anyString(),
+ any(IServiceCallback.class)));
+
+ doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+ eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class)));
+ doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+ eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class)));
+
+ when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock);
+ when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock);
+
+ when(mFmRadioModuleMock.getService()).thenReturn(mFmHalServiceMock);
+ when(mDabRadioModuleMock.getService()).thenReturn(mDabHalServiceMock);
+
+ when(mFmHalServiceMock.asBinder()).thenReturn(mFmBinderMock);
+ when(mDabHalServiceMock.asBinder()).thenReturn(mDabBinderMock);
+
+ doAnswer(invocation -> {
+ mFmDeathRecipient = (IBinder.DeathRecipient) invocation.getArguments()[0];
+ return null;
+ }).when(mFmBinderMock).linkToDeath(any(), anyInt());
+
+ when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock)))
+ .thenReturn(mFmTunerSessionMock);
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index cd1cd7e..7a8475f 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -19,9 +19,9 @@
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.anyInt;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,6 +47,8 @@
public final class RadioModuleTest {
private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT;
+ private static final RadioManager.ModuleProperties TEST_MODULE_PROPERTIES =
+ AidlTestUtils.makeDefaultModuleProperties();
// Mocks
@Mock
@@ -63,14 +65,7 @@
@Before
public void setup() throws RemoteException {
- mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
- /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
- /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
- /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
- /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
- /* supportedProgramTypes= */ new int[]{},
- /* supportedIdentifierTypes */ new int[]{},
- /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+ mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES, mLock);
// TODO(b/241118988): test non-null image for getImage method
when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
@@ -88,6 +83,12 @@
}
@Test
+ public void getProperties() {
+ assertWithMessage("Module properties of radio module")
+ .that(mRadioModule.getProperties()).isEqualTo(TEST_MODULE_PROPERTIES);
+ }
+
+ @Test
public void setInternalHalCallback_callbackSetInHal() throws Exception {
mRadioModule.setInternalHalCallback();
@@ -100,7 +101,7 @@
Bitmap imageTest = mRadioModule.getImage(imageId);
- assertWithMessage("Image got from radio module").that(imageTest).isNull();
+ assertWithMessage("Image from radio module").that(imageTest).isNull();
}
@Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 06d7cdd..3bf993c 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -19,10 +19,10 @@
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.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
@@ -93,13 +93,8 @@
@Before
public void setup() throws Exception {
- mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
- /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
- /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
- /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
- /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
- new int[] {}, new int[] {},
- /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+ mRadioModule = new RadioModule(mBroadcastRadioMock,
+ AidlTestUtils.makeDefaultModuleProperties(), mLock);
doAnswer(invocation -> {
mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
@@ -210,7 +205,7 @@
mTunerSessions[0].setMuted(/* mute= */ false);
- assertWithMessage("Session mute state after setting muted %s", false)
+ assertWithMessage("Session mute state after setting unmuted")
.that(mTunerSessions[0].isMuted()).isFalse();
}
@@ -220,7 +215,7 @@
mTunerSessions[0].setMuted(/* mute= */ true);
- assertWithMessage("Session mute state after setting muted %s", true)
+ assertWithMessage("Session mute state after setting muted")
.that(mTunerSessions[0].isMuted()).isTrue();
}
@@ -424,7 +419,7 @@
mTunerSessions[0].getImage(imageId);
});
- assertWithMessage("Exception for getting image with invalid ID")
+ assertWithMessage("Get image exception")
.that(thrown).hasMessageThat().contains("Image ID is missing");
}
@@ -467,7 +462,7 @@
boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
verify(mBroadcastRadioMock).isConfigFlagSet(flag);
- assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
+ assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
}
@Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/AnnouncementAggregatorHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/AnnouncementAggregatorHidlTest.java
new file mode 100644
index 0000000..b68e65f
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/AnnouncementAggregatorHidlTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.server.broadcastradio.hal2;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for HIDL 2.0 HAL AnnouncementAggregator.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class AnnouncementAggregatorHidlTest {
+
+ private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+ private final Object mLock = new Object();
+ private AnnouncementAggregator mAnnouncementAggregator;
+ private IBinder.DeathRecipient mDeathRecipient;
+
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private IBinder mBinderMock;
+ private RadioModule[] mRadioModuleMocks;
+ private ICloseHandle[] mCloseHandleMocks;
+ private Announcement[] mAnnouncementMocks;
+
+ @Before
+ public void setUp() throws Exception {
+ ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
+ ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+ when(mListenerMock.asBinder()).thenReturn(mBinderMock);
+
+ mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
+
+ verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), eq(0));
+ mDeathRecipient = deathRecipientCaptor.getValue();
+ }
+
+ @Test
+ public void onListUpdated_withOneModuleWatcher() throws Exception {
+ ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+ ArgumentCaptor.forClass(IAnnouncementListener.class);
+ watchModules(/* moduleNumber= */ 1);
+
+ verify(mRadioModuleMocks[0]).addAnnouncementListener(any(), moduleWatcherCaptor.capture());
+
+ moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[0]));
+
+ verify(mListenerMock).onListUpdated(any());
+ }
+
+ @Test
+ public void onListUpdated_withMultipleModuleWatchers() throws Exception {
+ int moduleNumber = 3;
+ watchModules(moduleNumber);
+
+ for (int index = 0; index < moduleNumber; index++) {
+ ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+ ArgumentCaptor.forClass(IAnnouncementListener.class);
+ ArgumentCaptor<List<Announcement>> announcementsCaptor =
+ ArgumentCaptor.forClass(List.class);
+ verify(mRadioModuleMocks[index])
+ .addAnnouncementListener(any(), moduleWatcherCaptor.capture());
+
+ moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
+
+ verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
+ assertWithMessage("Number of announcements %s after %s announcements were updated",
+ announcementsCaptor.getValue(), index + 1)
+ .that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
+ }
+ }
+
+ @Test
+ public void close_withOneModuleWatcher_invokesCloseHandle() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mAnnouncementAggregator.close();
+
+ verify(mCloseHandleMocks[0]).close();
+ verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
+ }
+
+ @Test
+ public void close_withMultipleModuleWatcher_invokesCloseHandles() throws Exception {
+ int moduleNumber = 3;
+ watchModules(moduleNumber);
+
+ mAnnouncementAggregator.close();
+
+ for (int index = 0; index < moduleNumber; index++) {
+ verify(mCloseHandleMocks[index]).close();
+ }
+ }
+
+ @Test
+ public void close_twice_invokesCloseHandleOnce() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mAnnouncementAggregator.close();
+ mAnnouncementAggregator.close();
+
+ verify(mCloseHandleMocks[0]).close();
+ verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
+ }
+
+ @Test
+ public void binderDied_forDeathRecipient_invokesCloseHandle() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mDeathRecipient.binderDied();
+
+ verify(mCloseHandleMocks[0]).close();
+
+ }
+
+ private void watchModules(int moduleNumber) throws RemoteException {
+ mRadioModuleMocks = new RadioModule[moduleNumber];
+ mCloseHandleMocks = new ICloseHandle[moduleNumber];
+ mAnnouncementMocks = new Announcement[moduleNumber];
+
+ for (int index = 0; index < moduleNumber; index++) {
+ mRadioModuleMocks[index] = mock(RadioModule.class);
+ mCloseHandleMocks[index] = mock(ICloseHandle.class);
+ mAnnouncementMocks[index] = mock(Announcement.class);
+
+ when(mRadioModuleMocks[index].addAnnouncementListener(any(), any()))
+ .thenReturn(mCloseHandleMocks[index]);
+ mAnnouncementAggregator.watchModule(mRadioModuleMocks[index], TEST_ENABLED_TYPES);
+ }
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
new file mode 100644
index 0000000..4d0b753
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.server.broadcastradio.hal2;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+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.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IBinder;
+import android.os.IHwBinder.DeathRecipient;
+import android.os.RemoteException;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public final class BroadcastRadioServiceHidlTest extends ExtendedRadioMockitoTestCase {
+
+ private static final int FM_RADIO_MODULE_ID = 0;
+ private static final int DAB_RADIO_MODULE_ID = 1;
+ private static final ArrayList<String> SERVICE_LIST =
+ new ArrayList<>(Arrays.asList("FmService", "DabService"));
+ private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+ private final Object mLock = new Object();
+
+ private BroadcastRadioService mBroadcastRadioService;
+ private DeathRecipient mFmDeathRecipient;
+
+ @Mock
+ private IServiceManager mServiceManagerMock;
+ @Mock
+ private RadioManager.ModuleProperties mFmModuleMock;
+ @Mock
+ private RadioManager.ModuleProperties mDabModuleMock;
+ @Mock
+ private RadioModule mFmRadioModuleMock;
+ @Mock
+ private RadioModule mDabRadioModuleMock;
+ @Mock
+ private IBroadcastRadio mFmHalServiceMock;
+ @Mock
+ private IBroadcastRadio mDabHalServiceMock;
+ @Mock
+ private TunerSession mFmTunerSessionMock;
+ @Mock
+ private ITunerCallback mTunerCallbackMock;
+ @Mock
+ private ICloseHandle mFmCloseHandleMock;
+ @Mock
+ private ICloseHandle mDabCloseHandleMock;
+ @Mock
+ private IAnnouncementListener mAnnouncementListenerMock;
+ @Mock
+ private IBinder mBinderMock;
+
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(RadioModule.class);
+ }
+
+ @Test
+ public void listModules_withMultipleServiceNames() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("Radio modules in HIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.listModules())
+ .containsExactly(mFmModuleMock, mDabModuleMock);
+ }
+
+ @Test
+ public void hasModules_withIdFoundInModules() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("DAB radio module in HIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isTrue();
+ }
+
+ @Test
+ public void hasModules_withIdNotFoundInModules() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("Radio module of id not found in HIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID + 1)).isFalse();
+ }
+
+ @Test
+ public void hasAnyModules_withModulesExist() throws Exception {
+ createBroadcastRadioService();
+
+ assertWithMessage("Any radio module in HIDL broadcast radio HAL client")
+ .that(mBroadcastRadioService.hasAnyModules()).isTrue();
+ }
+
+ @Test
+ public void openSession_withIdFound() throws Exception {
+ createBroadcastRadioService();
+
+ ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+
+ assertWithMessage("Session opened in FM radio module")
+ .that(session).isEqualTo(mFmTunerSessionMock);
+ }
+
+ @Test
+ public void openSession_withIdNotFound() throws Exception {
+ createBroadcastRadioService();
+ int moduleIdInvalid = DAB_RADIO_MODULE_ID + 1;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mBroadcastRadioService.openSession(moduleIdInvalid, /* legacyConfig= */ null,
+ /* withAudio= */ true, mTunerCallbackMock);
+ });
+
+ assertWithMessage("Exception for opening session with module id %s", moduleIdInvalid)
+ .that(thrown).hasMessageThat().contains("Invalid module ID");
+ }
+
+ @Test
+ public void addAnnouncementListener_addsOnAllRadioModules() throws Exception {
+ createBroadcastRadioService();
+ when(mAnnouncementListenerMock.asBinder()).thenReturn(mBinderMock);
+ when(mFmRadioModuleMock.addAnnouncementListener(any(), any()))
+ .thenReturn(mFmCloseHandleMock);
+ when(mDabRadioModuleMock.addAnnouncementListener(any(), any()))
+ .thenReturn(mDabCloseHandleMock);
+
+ mBroadcastRadioService.addAnnouncementListener(TEST_ENABLED_TYPES,
+ mAnnouncementListenerMock);
+
+ verify(mFmRadioModuleMock).addAnnouncementListener(any(), any());
+ verify(mDabRadioModuleMock).addAnnouncementListener(any(), any());
+ }
+
+ @Test
+ public void binderDied_forDeathRecipient() throws Exception {
+ createBroadcastRadioService();
+
+ mFmDeathRecipient.serviceDied(FM_RADIO_MODULE_ID);
+
+ verify(mFmRadioModuleMock).closeSessions(eq(RadioTuner.ERROR_HARDWARE_FAILURE));
+ assertWithMessage("FM radio module after FM broadcast radio HAL service died")
+ .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isFalse();
+ }
+
+ private void createBroadcastRadioService() throws RemoteException {
+ mockServiceManager();
+ mBroadcastRadioService = new BroadcastRadioService(/* nextModuleId= */ FM_RADIO_MODULE_ID,
+ mLock, mServiceManagerMock);
+ }
+
+ private void mockServiceManager() throws RemoteException {
+ doAnswer(invocation -> {
+ mFmDeathRecipient = (DeathRecipient) invocation.getArguments()[0];
+ return null;
+ }).when(mFmHalServiceMock).linkToDeath(any(), eq((long) FM_RADIO_MODULE_ID));
+
+ when(mServiceManagerMock.registerForNotifications(anyString(), anyString(),
+ any(IServiceNotification.class))).thenAnswer(invocation -> {
+ IServiceNotification serviceCallback =
+ (IServiceNotification) invocation.getArguments()[2];
+ for (int index = 0; index < SERVICE_LIST.size(); index++) {
+ serviceCallback.onRegistration(IBroadcastRadio.kInterfaceName,
+ SERVICE_LIST.get(index), /* b= */ false);
+ }
+ return true;
+ }).thenReturn(true);
+
+ doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+ eq(FM_RADIO_MODULE_ID), anyString(), any(Object.class)));
+ doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+ eq(DAB_RADIO_MODULE_ID), anyString(), any(Object.class)));
+
+ when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock);
+ when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock);
+
+ when(mFmRadioModuleMock.getService()).thenReturn(mFmHalServiceMock);
+ when(mDabRadioModuleMock.getService()).thenReturn(mDabHalServiceMock);
+
+ when(mFmRadioModuleMock.openSession(mTunerCallbackMock))
+ .thenReturn(mFmTunerSessionMock);
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java
new file mode 100644
index 0000000..3de4f5d
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.server.broadcastradio.hal2;
+
+import android.hardware.broadcastradio.V2_0.AmFmBandRange;
+import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.DabTableEntry;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
+import android.hardware.broadcastradio.V2_0.Properties;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public final class ConvertTest {
+
+ private static final int FM_LOWER_LIMIT = 87500;
+ private static final int FM_UPPER_LIMIT = 108000;
+ private static final int FM_SPACING = 200;
+ private static final int AM_LOWER_LIMIT = 540;
+ private static final int AM_UPPER_LIMIT = 1700;
+ private static final int AM_SPACING = 10;
+ private static final String DAB_ENTRY_LABEL_1 = "5A";
+ private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
+ private static final String DAB_ENTRY_LABEL_2 = "12D";
+ private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
+ private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
+ private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
+ private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
+ private static final String VENDOR_INFO_VALUE_2 = "vendorValue2";
+ private static final String TEST_SERVICE_NAME = "serviceMock";
+ private static final int TEST_ID = 1;
+ private static final String TEST_MAKER = "makerMock";
+ private static final String TEST_PRODUCT = "productMock";
+ private static final String TEST_VERSION = "versionMock";
+ private static final String TEST_SERIAL = "serialMock";
+
+ private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
+ private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
+
+ private static final RadioManager.ModuleProperties MODULE_PROPERTIES =
+ convertToModuleProperties();
+ private static final Announcement ANNOUNCEMENT =
+ Convert.announcementFromHal(
+ TestUtils.makeAnnouncement(TEST_ENABLED_TYPE, TEST_ANNOUNCEMENT_FREQUENCY));
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Test
+ public void propertiesFromHalProperties_idsMatch() {
+ expect.withMessage("Properties id")
+ .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_serviceNamesMatch() {
+ expect.withMessage("Service name")
+ .that(MODULE_PROPERTIES.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_implementorsMatch() {
+ expect.withMessage("Implementor")
+ .that(MODULE_PROPERTIES.getImplementor()).isEqualTo(TEST_MAKER);
+ }
+
+
+ @Test
+ public void propertiesFromHalProperties_productsMatch() {
+ expect.withMessage("Product")
+ .that(MODULE_PROPERTIES.getProduct()).isEqualTo(TEST_PRODUCT);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_versionsMatch() {
+ expect.withMessage("Version")
+ .that(MODULE_PROPERTIES.getVersion()).isEqualTo(TEST_VERSION);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_serialsMatch() {
+ expect.withMessage("Serial")
+ .that(MODULE_PROPERTIES.getSerial()).isEqualTo(TEST_SERIAL);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_dabTableInfoMatch() {
+ Map<String, Integer> dabTableExpected = Map.of(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1,
+ DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2);
+
+ expect.withMessage("Supported program types")
+ .that(MODULE_PROPERTIES.getDabFrequencyTable())
+ .containsExactlyEntriesIn(dabTableExpected);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_vendorInfoMatch() {
+ Map<String, String> vendorInfoExpected = Map.of(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1,
+ VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2);
+
+ expect.withMessage("Vendor info").that(MODULE_PROPERTIES.getVendorInfo())
+ .containsExactlyEntriesIn(vendorInfoExpected);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_bandsMatch() {
+ RadioManager.BandDescriptor[] bands = MODULE_PROPERTIES.getBands();
+
+ expect.withMessage("Band descriptors").that(bands).hasLength(2);
+
+ expect.withMessage("FM band frequency lower limit")
+ .that(bands[0].getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
+ expect.withMessage("FM band frequency upper limit")
+ .that(bands[0].getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
+ expect.withMessage("FM band frequency spacing")
+ .that(bands[0].getSpacing()).isEqualTo(FM_SPACING);
+
+ expect.withMessage("AM band frequency lower limit")
+ .that(bands[1].getLowerLimit()).isEqualTo(AM_LOWER_LIMIT);
+ expect.withMessage("AM band frequency upper limit")
+ .that(bands[1].getUpperLimit()).isEqualTo(AM_UPPER_LIMIT);
+ expect.withMessage("AM band frequency spacing")
+ .that(bands[1].getSpacing()).isEqualTo(AM_SPACING);
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_typesMatch() {
+ expect.withMessage("Announcement type")
+ .that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_selectorsMatch() {
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_ANNOUNCEMENT_FREQUENCY);
+
+ ProgramSelector selector = ANNOUNCEMENT.getSelector();
+
+ expect.withMessage("Primary id of announcement selector")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ expect.withMessage("Secondary ids of announcement selector")
+ .that(selector.getSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_VendorInfoMatch() {
+ expect.withMessage("Announcement vendor info")
+ .that(ANNOUNCEMENT.getVendorInfo()).isEmpty();
+ }
+
+ private static RadioManager.ModuleProperties convertToModuleProperties() {
+ AmFmRegionConfig amFmConfig = createAmFmRegionConfig();
+ List<DabTableEntry> dabTableEntries = Arrays.asList(
+ createDabTableEntry(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1),
+ createDabTableEntry(DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2));
+ Properties properties = createHalProperties();
+
+ return Convert.propertiesFromHal(TEST_ID, TEST_SERVICE_NAME, properties,
+ amFmConfig, dabTableEntries);
+ }
+
+ private static AmFmRegionConfig createAmFmRegionConfig() {
+ AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
+ amFmRegionConfig.ranges = new ArrayList<AmFmBandRange>(Arrays.asList(
+ createAmFmBandRange(FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING),
+ createAmFmBandRange(AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING)));
+ return amFmRegionConfig;
+ }
+
+ private static AmFmBandRange createAmFmBandRange(int lowerBound, int upperBound, int spacing) {
+ AmFmBandRange bandRange = new AmFmBandRange();
+ bandRange.lowerBound = lowerBound;
+ bandRange.upperBound = upperBound;
+ bandRange.spacing = spacing;
+ bandRange.scanSpacing = bandRange.spacing;
+ return bandRange;
+ }
+
+ private static DabTableEntry createDabTableEntry(String label, int value) {
+ DabTableEntry dabTableEntry = new DabTableEntry();
+ dabTableEntry.label = label;
+ dabTableEntry.frequency = value;
+ return dabTableEntry;
+ }
+
+ private static Properties createHalProperties() {
+ Properties halProperties = new Properties();
+ halProperties.supportedIdentifierTypes = new ArrayList<Integer>(Arrays.asList(
+ IdentifierType.AMFM_FREQUENCY, IdentifierType.RDS_PI, IdentifierType.DAB_SID_EXT));
+ halProperties.maker = TEST_MAKER;
+ halProperties.product = TEST_PRODUCT;
+ halProperties.version = TEST_VERSION;
+ halProperties.serial = TEST_SERIAL;
+ halProperties.vendorInfo = new ArrayList<VendorKeyValue>(Arrays.asList(
+ TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1),
+ TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2)));
+ return halProperties;
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java
new file mode 100644
index 0000000..48f5a46
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.server.broadcastradio.hal2;
+
+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.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.Constants;
+import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.RadioManager;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Tests for HIDL HAL RadioModule.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class RadioModuleHidlTest {
+
+ private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT;
+ private static final RadioManager.ModuleProperties TEST_MODULE_PROPERTIES =
+ TestUtils.makeDefaultModuleProperties();
+
+ @Mock
+ private IBroadcastRadio mBroadcastRadioMock;
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private android.hardware.broadcastradio.V2_0.ICloseHandle mHalCloseHandleMock;
+
+ private final Object mLock = new Object();
+ private RadioModule mRadioModule;
+ private android.hardware.broadcastradio.V2_0.IAnnouncementListener mHalListener;
+
+ @Before
+ public void setup() throws RemoteException {
+ mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES, mLock);
+
+ when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0));
+
+ doAnswer(invocation -> {
+ mHalListener = (android.hardware.broadcastradio.V2_0.IAnnouncementListener) invocation
+ .getArguments()[1];
+ IBroadcastRadio.registerAnnouncementListenerCallback cb =
+ (IBroadcastRadio.registerAnnouncementListenerCallback)
+ invocation.getArguments()[2];
+ cb.onValues(Result.OK, mHalCloseHandleMock);
+ return null;
+ }).when(mBroadcastRadioMock).registerAnnouncementListener(any(), any(), any());
+ }
+
+ @Test
+ public void getService() {
+ assertWithMessage("Service of radio module")
+ .that(mRadioModule.getService()).isEqualTo(mBroadcastRadioMock);
+ }
+
+ @Test
+ public void getProperties() {
+ assertWithMessage("Module properties of radio module")
+ .that(mRadioModule.getProperties()).isEqualTo(TEST_MODULE_PROPERTIES);
+ }
+
+ @Test
+ public void getImage_withValidIdFromRadioModule() {
+ int imageId = 1;
+
+ Bitmap imageTest = mRadioModule.getImage(imageId);
+
+ assertWithMessage("Image from radio module").that(imageTest).isNull();
+ }
+
+ @Test
+ public void getImage_withInvalidIdFromRadioModule_throwsIllegalArgumentException() {
+ int invalidImageId = Constants.INVALID_IMAGE;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mRadioModule.getImage(invalidImageId);
+ });
+
+ assertWithMessage("Exception for getting image with invalid ID")
+ .that(thrown).hasMessageThat().contains("Image ID is missing");
+ }
+
+ @Test
+ public void addAnnouncementListener_listenerRegistered() throws Exception {
+ ArrayList<Byte> enabledListExpected = new ArrayList<Byte>(Arrays.asList(
+ (byte) TEST_ENABLED_TYPE));
+ mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock);
+
+ verify(mBroadcastRadioMock)
+ .registerAnnouncementListener(eq(enabledListExpected), any(), any());
+ }
+
+ @Test
+ public void onListUpdate_forAnnouncementListener() throws Exception {
+ android.hardware.broadcastradio.V2_0.Announcement halAnnouncement =
+ TestUtils.makeAnnouncement(TEST_ENABLED_TYPE, /* selectorFreq= */ 96300);
+ mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock);
+
+ mHalListener.onListUpdated(
+ new ArrayList<android.hardware.broadcastradio.V2_0.Announcement>(
+ Arrays.asList(halAnnouncement)));
+
+ verify(mListenerMock).onListUpdated(any());
+ }
+
+ @Test
+ public void close_forCloseHandle() throws Exception {
+ ICloseHandle closeHandle =
+ mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock);
+
+ closeHandle.close();
+
+ verify(mHalCloseHandleMock).close();
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index 25bf93f..d104359 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -95,9 +95,8 @@
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
- mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(0, "",
- 0, "", "", "", "", 0, 0, false, false, null, false, new int[] {}, new int[] {},
- null, null), mLock);
+ mRadioModule = new RadioModule(mBroadcastRadioMock,
+ TestUtils.makeDefaultModuleProperties(), mLock);
doAnswer((Answer) invocation -> {
mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
index 4944803..4eedd2f 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
@@ -15,22 +15,61 @@
*/
package com.android.server.broadcastradio.hal2;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
+import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
+import android.util.ArrayMap;
+import java.util.ArrayList;
import java.util.HashMap;
final class TestUtils {
+
+ private TestUtils() {
+ throw new UnsupportedOperationException("TestUtils class is noninstantiable");
+ }
+
+ static RadioManager.ModuleProperties makeDefaultModuleProperties() {
+ return new RadioManager.ModuleProperties(
+ /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+ /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+ /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+ /* isCaptureSupported= */ false, /* bands= */ null,
+ /* isBgScanSupported= */ false, new int[] {}, new int[] {},
+ /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+ }
+
+ static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+ return new RadioManager.ProgramInfo(selector,
+ selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+ /* infoFlags= */ 0, signalQuality,
+ new RadioMetadata.Builder().build(), new ArrayMap<>());
+ }
+
static RadioManager.ProgramInfo makeProgramInfo(int programType,
ProgramSelector.Identifier identifier, int signalQuality) {
// Note: If you set new fields, check if programInfoToHal() needs to be updated as well.
- return new RadioManager.ProgramInfo(new ProgramSelector(programType, identifier, null,
- null), null, null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
+ return new RadioManager.ProgramInfo(makeProgramSelector(programType, identifier), null,
+ null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
new HashMap<String, String>());
}
+ static ProgramSelector makeFmSelector(long freq) {
+ return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ freq));
+ }
+
+ static ProgramSelector makeProgramSelector(int programType,
+ ProgramSelector.Identifier identifier) {
+ return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+ /* vendorIds= */ null);
+ }
+
static ProgramInfo programInfoToHal(RadioManager.ProgramInfo info) {
// Note that because Convert does not by design provide functions for all conversions, this
// function only copies fields that are set by makeProgramInfo().
@@ -39,4 +78,45 @@
hwInfo.signalQuality = info.getSignalStrength();
return hwInfo;
}
+
+ static android.hardware.broadcastradio.V2_0.ProgramSelector makeHalFmSelector(int freq) {
+ ProgramIdentifier halId = new ProgramIdentifier();
+ halId.type = IdentifierType.AMFM_FREQUENCY;
+ halId.value = freq;
+
+ android.hardware.broadcastradio.V2_0.ProgramSelector halSelector =
+ new android.hardware.broadcastradio.V2_0.ProgramSelector();
+ halSelector.primaryId = halId;
+ halSelector.secondaryIds = new ArrayList<ProgramIdentifier>();
+ return halSelector;
+ }
+
+ static ProgramInfo makeHalProgramInfo(
+ android.hardware.broadcastradio.V2_0.ProgramSelector hwSel, int hwSignalQuality) {
+ ProgramInfo hwInfo = new ProgramInfo();
+ hwInfo.selector = hwSel;
+ hwInfo.logicallyTunedTo = hwSel.primaryId;
+ hwInfo.physicallyTunedTo = hwSel.primaryId;
+ hwInfo.signalQuality = hwSignalQuality;
+ hwInfo.relatedContent = new ArrayList<>();
+ hwInfo.metadata = new ArrayList<>();
+ return hwInfo;
+ }
+
+ static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
+ VendorKeyValue vendorKeyValue = new VendorKeyValue();
+ vendorKeyValue.key = vendorKey;
+ vendorKeyValue.value = vendorValue;
+ return vendorKeyValue;
+ }
+
+ static android.hardware.broadcastradio.V2_0.Announcement makeAnnouncement(int type,
+ int selectorFreq) {
+ android.hardware.broadcastradio.V2_0.Announcement halAnnouncement =
+ new android.hardware.broadcastradio.V2_0.Announcement();
+ halAnnouncement.type = (byte) type;
+ halAnnouncement.selector = makeHalFmSelector(selectorFreq);
+ halAnnouncement.vendorInfo = new ArrayList<>();
+ return halAnnouncement;
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
new file mode 100644
index 0000000..936e606
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -0,0 +1,622 @@
+/*
+ * 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.server.broadcastradio.hal2;
+
+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.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.Constants;
+import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ITunerCallback;
+import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Tests for HIDL HAL TunerSession.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerSessionHidlTest {
+
+ private static final VerificationWithTimeout CALLBACK_TIMEOUT =
+ timeout(/* millis= */ 200);
+ private static final int SIGNAL_QUALITY = 1;
+ private static final long AM_FM_FREQUENCY_SPACING = 500;
+ private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+ private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
+ new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
+ /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
+ /* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
+ /* ea= */ false);
+ private static final RadioManager.BandConfig FM_BAND_CONFIG =
+ new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
+ private static final int UNSUPPORTED_CONFIG_FLAG = 0;
+
+ private final Object mLock = new Object();
+ private final ArrayMap<Integer, Boolean> mHalConfigMap = new ArrayMap<>();
+ private RadioModule mRadioModule;
+ private ITunerCallback mHalTunerCallback;
+ private ProgramInfo mHalCurrentInfo;
+ private TunerSession[] mTunerSessions;
+
+ @Mock private IBroadcastRadio mBroadcastRadioMock;
+ @Mock ITunerSession mHalTunerSessionMock;
+ private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+
+ @Before
+ public void setup() throws Exception {
+ mRadioModule = new RadioModule(mBroadcastRadioMock,
+ TestUtils.makeDefaultModuleProperties(), mLock);
+
+ doAnswer(invocation -> {
+ mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
+ IBroadcastRadio.openSessionCallback cb = (IBroadcastRadio.openSessionCallback)
+ invocation.getArguments()[1];
+ cb.onValues(Result.OK, mHalTunerSessionMock);
+ return null;
+ }).when(mBroadcastRadioMock).openSession(any(), any());
+
+ doAnswer(invocation -> {
+ android.hardware.broadcastradio.V2_0.ProgramSelector halSel =
+ (android.hardware.broadcastradio.V2_0.ProgramSelector)
+ invocation.getArguments()[0];
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+ if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY) {
+ return Result.NOT_SUPPORTED;
+ }
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mHalTunerSessionMock).tune(any());
+
+ doAnswer(invocation -> {
+ if ((boolean) invocation.getArguments()[0]) {
+ mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+ } else {
+ mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+ }
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mHalTunerSessionMock).step(anyBoolean());
+
+ doAnswer(invocation -> {
+ if (mHalCurrentInfo == null) {
+ android.hardware.broadcastradio.V2_0.ProgramSelector placeHolderSelector =
+ TestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+ mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+ return Result.OK;
+ }
+ mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+ mHalCurrentInfo.selector.primaryId.value,
+ !(boolean) invocation.getArguments()[0]);
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mHalTunerSessionMock).scan(anyBoolean(), anyBoolean());
+
+ when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0));
+
+ doAnswer(invocation -> {
+ int configFlag = (int) invocation.getArguments()[0];
+ ITunerSession.isConfigFlagSetCallback cb = (ITunerSession.isConfigFlagSetCallback)
+ invocation.getArguments()[1];
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+ cb.onValues(Result.NOT_SUPPORTED, false);
+ return null;
+ }
+ cb.onValues(Result.OK, mHalConfigMap.getOrDefault(configFlag, false));
+ return null;
+ }).when(mHalTunerSessionMock).isConfigFlagSet(anyInt(), any());
+
+ doAnswer(invocation -> {
+ int configFlag = (int) invocation.getArguments()[0];
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+ return Result.NOT_SUPPORTED;
+ }
+ mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
+ return Result.OK;
+ }).when(mHalTunerSessionMock).setConfigFlag(anyInt(), anyBoolean());
+ }
+
+ @After
+ public void cleanUp() {
+ mHalConfigMap.clear();
+ }
+
+ @Test
+ public void openSession_withMultipleSessions() throws Exception {
+ int numSessions = 3;
+
+ openAidlClients(numSessions);
+
+ for (int index = 0; index < numSessions; index++) {
+ assertWithMessage("Session of index %s close state", index)
+ .that(mTunerSessions[index].isClosed()).isFalse();
+ }
+ }
+
+ @Test
+ public void setConfiguration() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void getConfiguration() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+ RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
+
+ assertWithMessage("Session configuration").that(config)
+ .isEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void setMuted_withUnmuted() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].setMuted(/* mute= */ false);
+
+ assertWithMessage("Session mute state after setting unmuted")
+ .that(mTunerSessions[0].isMuted()).isFalse();
+ }
+
+ @Test
+ public void setMuted_withMuted() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].setMuted(/* mute= */ true);
+
+ assertWithMessage("Session mute state after setting muted")
+ .that(mTunerSessions[0].isMuted()).isTrue();
+ }
+
+ @Test
+ public void close_withOneSession() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].close();
+
+ assertWithMessage("Close state of broadcast radio service session")
+ .that(mTunerSessions[0].isClosed()).isTrue();
+ }
+
+ @Test
+ public void close_withOnlyOneSession_withMultipleSessions() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ int closeIdx = 0;
+
+ mTunerSessions[closeIdx].close();
+
+ for (int index = 0; index < numSessions; index++) {
+ if (index == closeIdx) {
+ assertWithMessage(
+ "Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isTrue();
+ } else {
+ assertWithMessage(
+ "Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isFalse();
+ }
+ }
+ }
+
+ @Test
+ public void close_withOneSession_withError() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int errorCode = RadioTuner.ERROR_SERVER_DIED;
+
+ mTunerSessions[0].close(errorCode);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
+ assertWithMessage("Close state of broadcast radio service session")
+ .that(mTunerSessions[0].isClosed()).isTrue();
+ }
+
+ @Test
+ public void closeSessions_withMultipleSessions_withError() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+
+ int errorCode = RadioTuner.ERROR_SERVER_DIED;
+ mRadioModule.closeSessions(errorCode);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
+ assertWithMessage("Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isTrue();
+ }
+ }
+
+ @Test
+ public void tune_withOneSession() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+ mTunerSessions[0].tune(initialSel);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+ }
+
+ @Test
+ public void tune_withMultipleSessions() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+ mTunerSessions[0].tune(initialSel);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(tuneInfo);
+ }
+ }
+
+ @Test
+ public void tune_withUnsupportedSelector_throwsException() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
+
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mTunerSessions[0].tune(unsupportedSelector));
+
+ assertWithMessage("Exception for tuning on unsupported program selector")
+ .that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
+ }
+
+ @Test
+ public void step_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo stepUpInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING), SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(stepUpInfo);
+ }
+
+ @Test
+ public void step_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo stepDownInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(stepDownInfo);
+ }
+
+ @Test
+ public void scan_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(scanUpInfo);
+ }
+
+ @Test
+ public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+ int numSessions = 2;
+ openAidlClients(numSessions);
+
+ mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onTuneFailed(eq(Result.TIMEOUT), any());
+ }
+ }
+
+ @Test
+ public void scan_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(scanUpInfo);
+ }
+
+ @Test
+ public void cancel() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ mTunerSessions[0].tune(initialSel);
+
+ mTunerSessions[0].cancel();
+
+ verify(mHalTunerSessionMock).cancel();
+ }
+
+ @Test
+ public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int imageId = Constants.INVALID_IMAGE;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mTunerSessions[0].getImage(imageId);
+ });
+
+ assertWithMessage("Get image exception")
+ .that(thrown).hasMessageThat().contains("Image ID is missing");
+ }
+
+ @Test
+ public void getImage_withValidId() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int imageId = 1;
+
+ Bitmap imageTest = mTunerSessions[0].getImage(imageId);
+
+ assertWithMessage("Null image").that(imageTest).isEqualTo(null);
+ }
+
+ @Test
+ public void startBackgroundScan() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].startBackgroundScan();
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete();
+ }
+
+ @Test
+ public void stopProgramListUpdates() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ mTunerSessions[0].startProgramListUpdates(aidlFilter);
+
+ mTunerSessions[0].stopProgramListUpdates();
+
+ verify(mHalTunerSessionMock).stopProgramListUpdates();
+ }
+
+ @Test
+ public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG;
+
+ boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
+
+ verify(mHalTunerSessionMock).isConfigFlagSet(eq(flag), any());
+ assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
+ }
+
+ @Test
+ public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+
+ boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
+
+ verify(mHalTunerSessionMock).isConfigFlagSet(eq(flag), any());
+ assertWithMessage("Config flag %s is supported", flag).that(isSupported).isTrue();
+ }
+
+ @Test
+ public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG;
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
+ });
+
+ assertWithMessage("Exception for setting unsupported flag %s", flag)
+ .that(thrown).hasMessageThat().contains("setConfigFlag: NOT_SUPPORTED");
+ }
+
+ @Test
+ public void setConfigFlag_withFlagSetToTrue() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+
+ mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
+
+ verify(mHalTunerSessionMock).setConfigFlag(flag, /* value= */ true);
+ }
+
+ @Test
+ public void setConfigFlag_withFlagSetToFalse() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+
+ mTunerSessions[0].setConfigFlag(flag, /* value= */ false);
+
+ verify(mHalTunerSessionMock).setConfigFlag(flag, /* value= */ false);
+ }
+
+ @Test
+ public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
+ throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG;
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mTunerSessions[0].isConfigFlagSet(flag);
+ });
+
+ assertWithMessage("Exception for check if unsupported flag %s is set", flag)
+ .that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED");
+ }
+
+ @Test
+ public void isConfigFlagSet_withSupportedFlag() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+ boolean expectedConfigFlagValue = true;
+ mTunerSessions[0].setConfigFlag(flag, /* value= */ expectedConfigFlagValue);
+
+ boolean isSet = mTunerSessions[0].isConfigFlagSet(flag);
+
+ assertWithMessage("Config flag %s is set", flag)
+ .that(isSet).isEqualTo(expectedConfigFlagValue);
+ }
+
+ @Test
+ public void setParameters_withMockParameters() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+ "mockParam2", "mockValue2");
+
+ mTunerSessions[0].setParameters(parametersSet);
+
+ verify(mHalTunerSessionMock).setParameters(Convert.vendorInfoToHal(parametersSet));
+ }
+
+ @Test
+ public void getParameters_withMockKeys() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ArrayList<String> parameterKeys = new ArrayList<>(Arrays.asList("mockKey1", "mockKey2"));
+
+ mTunerSessions[0].getParameters(parameterKeys);
+
+ verify(mHalTunerSessionMock).getParameters(parameterKeys);
+ }
+
+ @Test
+ public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+
+ mHalTunerCallback.onAntennaStateChange(/* connected= */ false);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onAntennaState(/* connected= */ false);
+ }
+ }
+
+ @Test
+ public void onParametersUpdated_forTunerCallback() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ ArrayList<VendorKeyValue> parametersUpdates = new ArrayList<VendorKeyValue>(Arrays.asList(
+ TestUtils.makeVendorKeyValue("com.vendor.parameter1", "value1")));
+ Map<String, String> parametersExpected = Map.of("com.vendor.parameter1", "value1");
+
+ mHalTunerCallback.onParametersUpdated(parametersUpdates);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onParametersUpdated(parametersExpected);
+ }
+ }
+
+ private void openAidlClients(int numClients) throws Exception {
+ mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
+ mTunerSessions = new TunerSession[numClients];
+ for (int index = 0; index < numClients; index++) {
+ mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
+ mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+ }
+ }
+
+ private long getSeekFrequency(long currentFrequency, boolean seekDown) {
+ long seekFrequency;
+ if (seekDown) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1];
+ for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) {
+ if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[i];
+ break;
+ }
+ }
+ } else {
+ seekFrequency = AM_FM_FREQUENCY_LIST[0];
+ for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) {
+ if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[index];
+ break;
+ }
+ }
+ }
+ return seekFrequency;
+ }
+}
diff --git a/core/tests/GameManagerTests/OWNERS b/core/tests/GameManagerTests/OWNERS
new file mode 100644
index 0000000..0992440
--- /dev/null
+++ b/core/tests/GameManagerTests/OWNERS
@@ -0,0 +1 @@
+include /GAME_MANAGER_OWNERS
\ No newline at end of file
diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index 1cf4302..372bca4 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -88,6 +88,7 @@
}
}
+ @SuppressWarnings("ParcelableCreator")
@SuppressLint("ParcelCreator")
private static class PointArray implements Parcelable {
Rect mBounds = new Rect();
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 04952bd..e2cdbf3 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -25,6 +25,11 @@
<option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
+ <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+ </target_preparer>
+
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
<option name="test-tag" value="FrameworksCoreTests" />
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index 7e875ad..10452fd 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -541,35 +541,35 @@
public void testBroadcastOption_interactive() throws Exception {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
try {
getContext().sendBroadcast(intent, null, options.toBundle());
- fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ fail("No exception thrown with BroadcastOptions.setInteractive(true)");
} catch (SecurityException se) {
// Expected, correct behavior - this case intentionally empty
} catch (Exception e) {
fail("Unexpected exception " + e.getMessage()
- + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ + " thrown with BroadcastOptions.setInteractive(true)");
}
}
public void testBroadcastOption_interactive_PendingIntent() throws Exception {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
PendingIntent brPending = PendingIntent.getBroadcast(getContext(),
1, intent, PendingIntent.FLAG_IMMUTABLE);
try {
brPending.send(getContext(), 1, null, null, null, null, options.toBundle());
- fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ fail("No exception thrown with BroadcastOptions.setInteractive(true)");
} catch (SecurityException se) {
// Expected, correct behavior - this case intentionally empty
} catch (Exception e) {
fail("Unexpected exception " + e.getMessage()
- + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ + " thrown with BroadcastOptions.setInteractive(true)");
} finally {
brPending.cancel();
}
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index 7f81339..5ac84f8 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -36,7 +36,8 @@
if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) {
resultString = "Successfully registered, but expected it to fail";
try {
- context.registerReceiver(this, new IntentFilter("foo.bar"));
+ context.registerReceiver(this, new IntentFilter("foo.bar"),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
context.unregisterReceiver(this);
} catch (ReceiverCallNotAllowedException e) {
//resultString = "This is the correct behavior but not yet implemented";
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index c89f37d..3f3d6a3 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -172,7 +172,7 @@
pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
mContext.unregisterReceiver(this);
}
- }, new IntentFilter(ACTION_SERVICE_STARTED));
+ }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
serviceTrigger.run();
try {
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 37cf470..4d5b0d2 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -46,6 +46,7 @@
public class BackupAgentTest {
// An arbitrary user.
private static final UserHandle USER_HANDLE = new UserHandle(15);
+ private static final String DATA_TYPE_BACKED_UP = "test data type";
@Mock FullBackup.BackupScheme mBackupScheme;
@@ -73,6 +74,42 @@
assertThat(rules).isEqualTo(expectedRules);
}
+ @Test
+ public void getBackupRestoreEventLogger_beforeOnCreate_isNull() {
+ BackupAgent agent = new TestFullBackupAgent();
+
+ assertThat(agent.getBackupRestoreEventLogger()).isNull();
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent()
+ throws Exception {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ // TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called.
+ agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0));
+
+ assertThat(agent.getBackupRestoreEventLogger().getLoggingResults().get(0).getDataType())
+ .isEqualTo(DATA_TYPE_BACKED_UP);
+ }
+
private BackupAgent getAgentForOperationType(@OperationType int operationType) {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, operationType);
@@ -88,6 +125,11 @@
}
@Override
+ public void onFullBackup(FullBackupDataOutput data) {
+ getBackupRestoreEventLogger().logItemsBackedUp(DATA_TYPE_BACKED_UP, 1);
+ }
+
+ @Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
// Left empty as this is a full backup agent.
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 67b24ec..b9fdc6d 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -25,6 +25,7 @@
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
@@ -35,6 +36,7 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -63,18 +65,22 @@
public void testBackupLogger_rejectsRestoreLogs() {
mLogger = new BackupRestoreEventLogger(BACKUP);
- assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
- assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata");
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
}
@Test
public void testRestoreLogger_rejectsBackupLogs() {
mLogger = new BackupRestoreEventLogger(RESTORE);
- assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
- assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+ mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata");
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
}
@Test
@@ -83,16 +89,17 @@
for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
String dataType = DATA_TYPE_1 + i;
- assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
- assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
- .isTrue();
- assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+ mLogger.logItemsBackedUp(dataType, /* count */ 5);
+ mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logBackupMetaData(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
}
- assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
- .isFalse();
- assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
}
@@ -102,16 +109,17 @@
for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
String dataType = DATA_TYPE_1 + i;
- assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
- assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
- .isTrue();
- assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+ mLogger.logItemsRestored(dataType, /* count */ 5);
+ mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
}
- assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
- .isFalse();
- assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
}
@@ -230,10 +238,10 @@
mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
- int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_1);
- int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_2);
+ int firstErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
+ int secondErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
@@ -247,16 +255,54 @@
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
- int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_1);
- int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_2);
+ int firstErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
+ int secondErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
- private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
- @BackupRestoreDataType String dataType) {
+ @Test
+ public void testGetLoggingResults_resultsParceledAndUnparceled_recreatedCorrectly() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+ int firstTypeSuccessCount = 1;
+ int firstTypeErrorOneCount = 2;
+ int firstTypeErrorTwoCount = 3;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstTypeSuccessCount);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorOneCount, ERROR_1);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorTwoCount, ERROR_2);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+ int secondTypeSuccessCount = 4;
+ int secondTypeErrorOneCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_2, secondTypeSuccessCount);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_2, secondTypeErrorOneCount, ERROR_1);
+
+ List<DataTypeResult> resultsList = mLogger.getLoggingResults();
+ Parcel parcel = Parcel.obtain();
+
+ parcel.writeParcelableList(resultsList, /* flags= */ 0);
+
+ parcel.setDataPosition(0);
+ List<DataTypeResult> recreatedList = new ArrayList<>();
+ parcel.readParcelableList(
+ recreatedList, DataTypeResult.class.getClassLoader(), DataTypeResult.class);
+
+ assertThat(recreatedList.get(0).getDataType()).isEqualTo(DATA_TYPE_1);
+ assertThat(recreatedList.get(0).getSuccessCount()).isEqualTo(firstTypeSuccessCount);
+ assertThat(recreatedList.get(0).getFailCount())
+ .isEqualTo(firstTypeErrorOneCount + firstTypeErrorTwoCount);
+ assertThat(recreatedList.get(0).getErrors().get(ERROR_1)).isEqualTo(firstTypeErrorOneCount);
+ assertThat(recreatedList.get(0).getErrors().get(ERROR_2)).isEqualTo(firstTypeErrorTwoCount);
+ assertThat(recreatedList.get(1).getDataType()).isEqualTo(DATA_TYPE_2);
+ assertThat(recreatedList.get(1).getSuccessCount()).isEqualTo(secondTypeSuccessCount);
+ assertThat(recreatedList.get(1).getFailCount()).isEqualTo(secondTypeErrorOneCount);
+ assertThat(recreatedList.get(1).getErrors().get(ERROR_1))
+ .isEqualTo(secondTypeErrorOneCount);
+ }
+
+ private static DataTypeResult getResultForDataType(
+ BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
if (result.isEmpty()) {
fail("Failed to find result for data type: " + dataType);
@@ -267,8 +313,9 @@
private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
List<DataTypeResult> resultList = logger.getLoggingResults();
- return resultList.stream().filter(
- dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
+ return resultList.stream()
+ .filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType))
+ .findAny();
}
private byte[] getMetaDataHash(String metaData) {
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index b292d7d..a0ed026 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -294,10 +294,9 @@
StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
- IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
- ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+ ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
transaction.addCallback(callback1);
transaction.addCallback(callback2);
transaction.setLifecycleStateRequest(lifecycleRequest);
@@ -318,10 +317,9 @@
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
config());
- IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
- ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+ ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
transaction.addCallback(callback1);
transaction.addCallback(callback2);
@@ -339,10 +337,9 @@
// Write to parcel
StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
- IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
- ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+ ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
transaction.setLifecycleStateRequest(lifecycleRequest);
writeAndPrepareForReading(transaction);
@@ -400,286 +397,4 @@
}
};
}
-
- /** Stub implementation of IApplicationThread that can be presented as {@link Binder}. */
- class StubAppThread extends android.app.IApplicationThread.Stub {
-
- @Override
- public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
- }
-
- @Override
- public void scheduleReceiver(Intent intent, ActivityInfo activityInfo,
- CompatibilityInfo compatibilityInfo, int i, String s, Bundle bundle, boolean b,
- int i1, int i2) throws RemoteException {
- }
-
- @Override
- public void scheduleCreateService(IBinder iBinder, ServiceInfo serviceInfo,
- CompatibilityInfo compatibilityInfo, int i) throws RemoteException {
- }
-
- @Override
- public void scheduleStopService(IBinder iBinder) throws RemoteException {
- }
-
- @Override
- public void bindApplication(String s, ApplicationInfo applicationInfo,
- String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
- ProviderInfoList list, ComponentName componentName, ProfilerInfo profilerInfo,
- Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher,
- IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
- boolean b2, boolean b3, Configuration configuration,
- CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1,
- AutofillOptions ao, ContentCaptureOptions co, long[] disableCompatChanges,
- SharedMemory serializedSystemFontMap,
- long startRequestedElapsedTime, long startRequestedUptime)
- throws RemoteException {
- }
-
- @Override
- public void scheduleExit() throws RemoteException {
- }
-
- @Override
- public void scheduleServiceArgs(IBinder iBinder, ParceledListSlice parceledListSlice)
- throws RemoteException {
- }
-
- @Override
- public void updateTimeZone() throws RemoteException {
- }
-
- @Override
- public void processInBackground() throws RemoteException {
- }
-
- @Override
- public void scheduleBindService(IBinder iBinder, Intent intent, boolean b, int i)
- throws RemoteException {
- }
-
- @Override
- public void scheduleUnbindService(IBinder iBinder, Intent intent) throws RemoteException {
- }
-
- @Override
- public void dumpService(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
- String[] strings) throws RemoteException {
- }
-
- @Override
- public void scheduleRegisteredReceiver(IIntentReceiver iIntentReceiver, Intent intent,
- int i, String s, Bundle bundle, boolean b, boolean b1, int i1, int i2)
- throws RemoteException {
- }
-
- @Override
- public void scheduleLowMemory() throws RemoteException {
- }
-
- @Override
- public void profilerControl(boolean b, ProfilerInfo profilerInfo, int i)
- throws RemoteException {
- }
-
- @Override
- public void setSchedulingGroup(int i) throws RemoteException {
- }
-
- @Override
- public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo,
- int i, int userId, int operatioType)
- throws RemoteException {
- }
-
- @Override
- public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo,
- int userId) throws RemoteException {
- }
-
- @Override
- public void scheduleOnNewActivityOptions(IBinder iBinder, Bundle bundle)
- throws RemoteException {
- }
-
- @Override
- public void scheduleSuicide() throws RemoteException {
- }
-
- @Override
- public void dispatchPackageBroadcast(int i, String[] strings) throws RemoteException {
- }
-
- @Override
- public void scheduleCrash(String s, int i, Bundle extras) throws RemoteException {
- }
-
- @Override
- public void dumpActivity(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
- String s, String[] strings) throws RemoteException {
- }
-
- @Override
- public void clearDnsCache() throws RemoteException {
- }
-
- @Override
- public void updateHttpProxy() throws RemoteException {
- }
-
- @Override
- public void setCoreSettings(Bundle bundle) throws RemoteException {
- }
-
- @Override
- public void updatePackageCompatibilityInfo(String s, CompatibilityInfo compatibilityInfo)
- throws RemoteException {
- }
-
- @Override
- public void scheduleTrimMemory(int i) throws RemoteException {
- }
-
- @Override
- public void dumpMemInfo(ParcelFileDescriptor parcelFileDescriptor,
- Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, boolean b3,
- boolean b4, String[] strings) throws RemoteException {
- }
-
- @Override
- public void dumpMemInfoProto(ParcelFileDescriptor parcelFileDescriptor,
- Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2,
- boolean b3, String[] strings) throws RemoteException {
- }
-
- @Override
- public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
- throws RemoteException {
- }
-
- @Override
- public void dumpCacheInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
- throws RemoteException {
- }
-
- @Override
- public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
- String[] strings) throws RemoteException {
- }
-
- @Override
- public void dumpDbInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
- throws RemoteException {
- }
-
- @Override
- public void unstableProviderDied(IBinder iBinder) throws RemoteException {
- }
-
- @Override
- public void requestAssistContextExtras(IBinder iBinder, IBinder iBinder1, int i, int i1,
- int i2) throws RemoteException {
- }
-
- @Override
- public void scheduleTranslucentConversionComplete(IBinder iBinder, boolean b)
- throws RemoteException {
- }
-
- @Override
- public void setProcessState(int i) throws RemoteException {
- }
-
- @Override
- public void scheduleInstallProvider(ProviderInfo providerInfo) throws RemoteException {
- }
-
- @Override
- public void updateTimePrefs(int i) throws RemoteException {
- }
-
- @Override
- public void scheduleEnterAnimationComplete(IBinder iBinder) throws RemoteException {
- }
-
- @Override
- public void notifyCleartextNetwork(byte[] bytes) throws RemoteException {
- }
-
- @Override
- public void startBinderTracking() throws RemoteException {
- }
-
- @Override
- public void stopBinderTrackingAndDump(ParcelFileDescriptor parcelFileDescriptor)
- throws RemoteException {
- }
-
- @Override
- public void scheduleLocalVoiceInteractionStarted(IBinder iBinder,
- IVoiceInteractor iVoiceInteractor) throws RemoteException {
- }
-
- @Override
- public void handleTrustStorageUpdate() throws RemoteException {
- }
-
- @Override
- public void attachAgent(String s) throws RemoteException {
- }
-
- @Override
- public void attachStartupAgents(String s) throws RemoteException {
- }
-
- @Override
- public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo)
- throws RemoteException {
- }
-
- @Override
- public void setNetworkBlockSeq(long l) throws RemoteException {
- }
-
- @Override
- public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
- ParcelFileDescriptor fd, RemoteCallback finishCallback) {
- }
-
- @Override
- public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) {
- }
-
- @Override
- public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
- }
-
- @Override
- public void requestDirectActions(IBinder activityToken, IVoiceInteractor interactor,
- RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
- }
-
- @Override
- public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments,
- RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
- }
-
- @Override
- public void notifyContentProviderPublishStatus(ContentProviderHolder holder, String auth,
- int userId, boolean published) {
- }
-
- @Override
- public void instrumentWithoutRestart(ComponentName instrumentationName,
- Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- IUiAutomationConnection instrumentationUiConnection, ApplicationInfo targetInfo) {
- }
-
- @Override
- public void updateUiTranslationState(IBinder activityToken, int state,
- TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
- UiTranslationSpec uiTranslationSpec) {
- }
- }
}
diff --git a/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
new file mode 100644
index 0000000..f57ee43
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_UNKNOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.DetectorStatusTypes.DetectorStatus;
+
+import org.junit.Test;
+
+public class DetectorStatusTypesTest {
+
+ @Test
+ public void testRequireValidDetectionAlgorithmStatus() {
+ for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.requireValidDetectionAlgorithmStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+ }
+
+ @Test
+ public void testFormatAndParseDetectionAlgorithmStatus() {
+ for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.detectionAlgorithmStatusFromString(
+ DetectorStatusTypes.detectionAlgorithmStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(
+ DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(
+ DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+ }
+
+ @Test
+ public void testRequireValidDetectorStatus() {
+ for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+ status <= DETECTOR_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.requireValidDetectorStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_RUNNING + 1));
+ }
+
+ @Test
+ public void testFormatAndParseDetectorStatus() {
+ for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+ status <= DETECTOR_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.detectorStatusFromString(
+ DetectorStatusTypes.detectorStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_RUNNING + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 0000000..a648a88
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+public class LocationTimeZoneAlgorithmStatusTest {
+
+ private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_RUNNING_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ @Test
+ public void testConstructorValidation() {
+ // Sample some invalid cases
+
+ // There can't be a reported provider status if the algorithm isn't running.
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null));
+
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+ // No reported provider status expected if the associated provider isn't ready / present.
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null));
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS));
+ }
+
+ @Test
+ public void testEquals() {
+ LocationTimeZoneAlgorithmStatus one = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ LocationTimeZoneAlgorithmStatus two = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ LocationTimeZoneAlgorithmStatus three = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Primary provider only.
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Secondary provider only
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Algorithm not running.
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+
+ @Test
+ public void testRequireValidProviderStatus() {
+ for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+ status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+ PROVIDER_STATUS_NOT_PRESENT - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+ PROVIDER_STATUS_IS_UNCERTAIN + 1));
+ }
+
+ @Test
+ public void testFormatAndParseProviderStatus() {
+ for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+ status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+ assertEquals(status, LocationTimeZoneAlgorithmStatus.providerStatusFromString(
+ LocationTimeZoneAlgorithmStatus.providerStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+ PROVIDER_STATUS_NOT_PRESENT - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+ PROVIDER_STATUS_IS_UNCERTAIN + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString("FOO"));
+ }
+
+ @Test
+ public void testParseCommandlineArg_noNullReportedStatuses() {
+ LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+ }
+
+ @Test
+ public void testParseCommandlineArg_withNullReportedStatuses() {
+ LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, null);
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
similarity index 82%
rename from core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
rename to core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
index 0073d86..13e5e14 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
+++ b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
import static org.junit.Assert.assertEquals;
@@ -48,6 +48,13 @@
}
public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
- assertEquals(instance, roundTripParcelable(instance));
+ assertEqualsAndHashCode(instance, roundTripParcelable(instance));
+ }
+
+ /** Asserts that the objects are equal and return identical hash codes. */
+ public static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(two, one);
+ assertEquals(one.hashCode(), two.hashCode());
}
}
diff --git a/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 0000000..b90c485
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TelephonyTimeZoneAlgorithmStatusTest {
+
+ @Test
+ public void testEquals() {
+ TelephonyTimeZoneAlgorithmStatus one = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ TelephonyTimeZoneAlgorithmStatus two = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ TelephonyTimeZoneAlgorithmStatus three = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Algorithm running.
+ {
+ TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Algorithm not running.
+ {
+ TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
index c9b96c6..1a276ad 100644
--- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -21,7 +21,8 @@
import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static com.google.common.truth.Truth.assertThat;
@@ -55,7 +56,7 @@
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
- assertEquals(one, two);
+ assertEqualsAndHashCode(one, two);
}
builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -69,7 +70,7 @@
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
- assertEquals(one, two);
+ assertEqualsAndHashCode(one, two);
}
builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
@@ -83,7 +84,7 @@
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
- assertEquals(one, two);
+ assertEqualsAndHashCode(one, two);
}
}
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
index bce0909..25e6e2b 100644
--- a/core/tests/coretests/src/android/app/time/TimeStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -16,7 +16,8 @@
package android.app.time;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
assertNotEquals(time1False_1, time2False);
}
- private static void assertEqualsAndHashCode(Object one, Object two) {
- assertEquals(one, two);
- assertEquals(one.hashCode(), two.hashCode());
- }
-
@Test
public void testParceling() {
UnixEpochTime time = new UnixEpochTime(1, 2);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 3f7da8a..8bed31f 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -18,7 +18,7 @@
import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static com.google.common.truth.Truth.assertThat;
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
new file mode 100644
index 0000000..dfff7ec
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TimeZoneDetectorStatusTest {
+
+ private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_ALGORITHM_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT, null);
+
+ @Test
+ public void testEquals() {
+ TimeZoneDetectorStatus one = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ TimeZoneDetectorStatus two = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+
+ {
+ TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertNotEquals(telephonyAlgorithmStatus, ARBITRARY_TELEPHONY_ALGORITHM_STATUS);
+
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ telephonyAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null);
+ assertNotEquals(locationAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, locationAlgorithmStatus);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Detector running.
+ {
+ TimeZoneDetectorStatus locationAlgorithmStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+ ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Detector not running.
+ {
+ TimeZoneDetectorStatus locationAlgorithmStatus =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+ ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
index 35a9dbc..595b700 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -16,7 +16,8 @@
package android.app.time;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
assertNotEquals(zone1False_1, zone2False);
}
- private static void assertEqualsAndHashCode(Object one, Object two) {
- assertEquals(one, two);
- assertEquals(one.hashCode(), two.hashCode());
- }
-
@Test
public void testParceling() {
assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index 0c7c8c1..28da164 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index 26cb902..e9ca069 100644
--- a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
index 17838bb1..b5bdea7 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timezonedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
index 28009d4..d5dcac2 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timezonedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
index 98485c0..ee73f00 100644
--- a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
+++ b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
@@ -29,10 +29,11 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
/**
- * Test class for {@link ConstrainDisplayApisConfig}.
+ * Test for {@link ConstrainDisplayApisConfig}.
*
* Build/Install/Run:
* atest FrameworksCoreTests:ConstrainDisplayApisConfigTest
@@ -72,6 +73,7 @@
testNeverConstrainDisplayApis("com.android.test", /* version= */ 1, /* expected= */ false);
}
+ @Ignore("b/257375674")
@Test
public void neverConstrainDisplayApis_flagsHasSingleEntry_returnsTrueForPackageWithinRange() {
setNeverConstrainDisplayApisFlag("com.android.test:1:1");
@@ -107,6 +109,7 @@
testNeverConstrainDisplayApis("com.android.test4", /* version= */ 9, /* expected= */ false);
}
+ @Ignore("b/257375674")
@Test
public void neverConstrainDisplayApis_flagHasInvalidEntries_ignoresInvalidEntries() {
// We add a valid entry before and after the invalid ones to make sure they are applied.
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
index d505492..86e95832 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import android.content.pm.PackageManager.Property;
@@ -162,40 +163,30 @@
@Test
public void testProperty_invalidName() throws Exception {
- try {
+ assertThrows(NullPointerException.class, () -> {
final Property p = new Property(null, 1, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
}
@Test
public void testProperty_invalidType() throws Exception {
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
final Property p = new Property("invalidTypeProperty", 0, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
final Property p = new Property("invalidTypeProperty", 6, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
final Property p = new Property("invalidTypeProperty", -1, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
}
@Test
public void testProperty_noPackageName() throws Exception {
- try {
+ assertThrows(NullPointerException.class, () -> {
final Property p = new Property(null, 1, null, null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
}
}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 69eb13f..d1d14f6 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -114,6 +114,23 @@
}
@Test
+ public void testSendHint() {
+ Session s = createSession();
+ assumeNotNull(s);
+ s.sendHint(Session.CPU_LOAD_UP);
+ s.sendHint(Session.CPU_LOAD_RESET);
+ }
+
+ @Test
+ public void testSendHintWithNegativeHint() {
+ Session s = createSession();
+ assumeNotNull(s);
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.sendHint(-1);
+ });
+ }
+
+ @Test
public void testCloseHintSession() {
Session s = createSession();
assumeNotNull(s);
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 7ebebc9..c59a3f5 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -246,10 +246,12 @@
@Test
public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(1f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(2f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
.build();
@@ -258,6 +260,7 @@
assertTrue(Float.isNaN(info.getQFactor()));
assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
// One vibrator with values undefined.
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
@@ -266,16 +269,19 @@
assertTrue(Float.isNaN(info.getQFactor()));
assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(10f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 11, 10, 0.5f, null))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(10f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 11, 5, 1, null))
@@ -285,113 +291,131 @@
assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+
+ // No frequency range defined.
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
@Test
public void getFrequencyProfile_noVibrator_returnsEmpty() {
VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, differentResonantFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_missingValues_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingResonantFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingMinFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
new float[] { 0.5f, 1, 1, 0.5f }))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 1, 1, 1 }))
.build();
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 0.8f, 1, 0.8f, 0.5f }))
.build();
@@ -401,6 +425,20 @@
assertEquals(
new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
info.getFrequencyProfile());
+ assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+
+ // Third vibrator without frequency control capability.
+ thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
@Test
@@ -547,4 +585,12 @@
VibrationAttributes vibrationAttributes = captor.getValue();
assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
}
+
+ /**
+ * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
+ */
+ void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
}
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
index ab63f14..7f772dd 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -16,14 +16,19 @@
package android.service.timezone;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -34,12 +39,92 @@
public class TimeZoneProviderEventTest {
+ public static final TimeZoneProviderStatus ARBITRARY_TIME_ZONE_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ @Test
+ public void createPermanentFailure() {
+ long creationElapsedMillis = 1111L;
+ String cause = "Cause";
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
+ creationElapsedMillis, cause);
+
+ assertEquals(EVENT_TYPE_PERMANENT_FAILURE, event.getType());
+ assertEquals(cause, event.getFailureCause());
+ assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+ assertNull(event.getSuggestion());
+ assertNull(event.getTimeZoneProviderStatus());
+ }
+
+ @Test
+ public void createSuggestion() {
+ long creationElapsedMillis = 1111L;
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(2222L)
+ .setTimeZoneIds(Collections.singletonList("Europe/London"))
+ .build();
+
+ TimeZoneProviderStatus reportedStatus = new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ assertThrows(NullPointerException.class, () -> TimeZoneProviderEvent.createSuggestionEvent(
+ creationElapsedMillis, /*suggestion=*/null, reportedStatus));
+
+ // Only TimeZoneProvider can report itself certain.
+ {
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+ creationElapsedMillis, suggestion, reportedStatus);
+ assertEquals(EVENT_TYPE_SUGGESTION, event.getType());
+ assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+ assertNull(event.getFailureCause());
+ assertEquals(suggestion, event.getSuggestion());
+ }
+
+ // Legacy API events can be created where the TimeZoneProviderStatus is omitted.
+ {
+ TimeZoneProviderStatus legacyStatus = null;
+ TimeZoneProviderEvent legacyEvent = TimeZoneProviderEvent.createSuggestionEvent(
+ creationElapsedMillis, suggestion, legacyStatus);
+ assertEquals(legacyStatus, legacyEvent.getTimeZoneProviderStatus());
+ }
+ }
+
+ @Test
+ public void createUncertain() {
+ long creationElapsedMillis = 1111L;
+
+ // The TimeZoneProvider can report itself uncertain.
+ {
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
+ creationElapsedMillis, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+ assertEquals(EVENT_TYPE_UNCERTAIN, event.getType());
+ assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+ assertNull(event.getFailureCause());
+ assertNull(event.getSuggestion());
+ }
+
+ // Legacy API events can be created where the TimeZoneProviderStatus is omitted.
+ {
+ TimeZoneProviderStatus legacyStatus = null;
+ TimeZoneProviderEvent legacyEvent = TimeZoneProviderEvent.createUncertainEvent(
+ creationElapsedMillis, legacyStatus);
+ assertEquals(legacyStatus, legacyEvent.getTimeZoneProviderStatus());
+ }
+ }
+
@Test
public void isEquivalentToAndEquals() {
long creationElapsedMillis = 1111L;
TimeZoneProviderEvent failEvent =
TimeZoneProviderEvent.createPermanentFailureEvent(creationElapsedMillis, "one");
- TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
+ TimeZoneProviderStatus providerStatus = ARBITRARY_TIME_ZONE_PROVIDER_STATUS;
TimeZoneProviderEvent uncertainEvent =
TimeZoneProviderEvent.createUncertainEvent(creationElapsedMillis, providerStatus);
@@ -85,14 +170,14 @@
@Test
public void isEquivalentToAndEquals_uncertain() {
TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
.build();
TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
.build();
TimeZoneProviderEvent uncertain1v1 =
@@ -123,14 +208,14 @@
@Test
public void isEquivalentToAndEquals_suggestion() {
TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
.build();
TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
.build();
TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder()
.setElapsedRealtimeMillis(1111L)
@@ -194,7 +279,13 @@
@Test
public void testParcelable_uncertain() {
TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
- 1111L, TimeZoneProviderStatus.UNKNOWN);
+ 1111L, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+ assertRoundTripParcelable(event);
+ }
+
+ @Test
+ public void testParcelable_uncertain_legacy() {
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L, null);
assertRoundTripParcelable(event);
}
@@ -204,7 +295,17 @@
.setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
.build();
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
- 1111L, suggestion, TimeZoneProviderStatus.UNKNOWN);
+ 1111L, suggestion, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+ assertRoundTripParcelable(event);
+ }
+
+ @Test
+ public void testParcelable_suggestion_legacy() {
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
+ .build();
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+ 1111L, suggestion, null);
assertRoundTripParcelable(event);
}
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index d61c33c..9006cd9 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,95 +16,25 @@
package android.service.timezone;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThrows;
import org.junit.Test;
+/** Non-SDK tests. See CTS for SDK API tests. */
public class TimeZoneProviderStatusTest {
@Test
- public void testStatusValidation() {
+ public void parseProviderStatus() {
TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
.build();
- assertThrows(IllegalArgumentException.class,
- () -> new TimeZoneProviderStatus.Builder(status)
- .setLocationDetectionStatus(-1)
- .build());
- assertThrows(IllegalArgumentException.class,
- () -> new TimeZoneProviderStatus.Builder(status)
- .setConnectivityStatus(-1)
- .build());
- assertThrows(IllegalArgumentException.class,
- () -> new TimeZoneProviderStatus.Builder(status)
- .setTimeZoneResolutionStatus(-1)
- .build());
- }
-
- @Test
- public void testEqualsAndHashcode() {
- TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
- .build();
- assertEqualsAndHashcode(status1_1, status1_1);
- assertNotEquals(status1_1, null);
-
- {
- TimeZoneProviderStatus status1_2 =
- new TimeZoneProviderStatus.Builder(status1_1).build();
- assertEqualsAndHashcode(status1_1, status1_2);
- assertNotSame(status1_1, status1_2);
- }
-
- {
- TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
- .setLocationDetectionStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
- .build();
- assertNotEquals(status1_1, status2);
- }
-
- {
- TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
- .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
- .build();
- assertNotEquals(status1_1, status2);
- }
-
- {
- TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
- .build();
- assertNotEquals(status1_1, status2);
- }
- }
-
- private static void assertEqualsAndHashcode(Object one, Object two) {
- assertEquals(one, two);
- assertEquals(two, one);
- assertEquals(one.hashCode(), two.hashCode());
- }
-
- @Test
- public void testParcelable() {
- TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
- .build();
- assertRoundTripParcelable(status);
+ assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
}
}
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44..8459330 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -156,8 +156,8 @@
@DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
public void testGetBestDateTimePattern_enableDuplicateField() {
// en-US uses 12-hour format by default.
- assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
- assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+ assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+ assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
}
private static void assertIllegalArgumentException(Locale l, String skeleton) {
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9c06395..de7244d 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -93,7 +93,8 @@
assertEquals("January 19",
formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
FORMAT_SHOW_DATE));
- assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME));
+ assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+ FORMAT_SHOW_TIME));
assertEquals("January 19, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR));
assertEquals("January 19",
@@ -101,27 +102,27 @@
assertEquals("January",
formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
FORMAT_NO_MONTH_DAY));
- assertEquals("3:30 AM",
+ assertEquals("3:30\u202fAM",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_12HOUR | FORMAT_SHOW_TIME));
assertEquals("03:30",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_24HOUR | FORMAT_SHOW_TIME));
- assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+ assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
FORMAT_12HOUR /*| FORMAT_CAP_AMPM*/ | FORMAT_SHOW_TIME));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_CAP_NOON*/));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR /*| FORMAT_NO_NOON*/ | FORMAT_SHOW_TIME));
- assertEquals("12:00 AM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
+ assertEquals("12:00\u202fAM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
fixedTime - midnightDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_NO_MIDNIGHT*/));
- assertEquals("3:30 AM",
+ assertEquals("3:30\u202fAM",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME | FORMAT_UTC));
- assertEquals("3 AM", formatDateRange(en_US, tz, onTheHour, onTheHour,
+ assertEquals("3\u202fAM", formatDateRange(en_US, tz, onTheHour, onTheHour,
FORMAT_SHOW_TIME | FORMAT_ABBREV_TIME));
assertEquals("Mon", formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY));
@@ -134,13 +135,13 @@
assertEquals("1/19/2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * HOUR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 1/22/2009",
+ assertEquals("1/19/2009\u2009\u2013\u20091/22/2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 4/22/2009",
+ assertEquals("1/19/2009\u2009\u2013\u20094/22/2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 2/9/2012",
+ assertEquals("1/19/2009\u2009\u2013\u20092/9/2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
@@ -151,7 +152,7 @@
assertEquals("19.01. – 22.04.2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19.01.2009 – 09.02.2012",
+ assertEquals("19.01.2009\u2009\u2013\u200909.02.2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
@@ -169,48 +170,48 @@
assertEquals("19/1/2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + HOUR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 22/1/2009",
+ assertEquals("19/1/2009\u2009\u2013\u200922/1/2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 22/4/2009",
+ assertEquals("19/1/2009\u2009\u2013\u200922/4/2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 9/2/2012",
+ assertEquals("19/1/2009\u2009\u2013\u20099/2/2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
// These are some random other test cases I came up with.
- assertEquals("January 19 – 22, 2009",
+ assertEquals("January 19\u2009\u2013\u200922, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, 0));
- assertEquals("Jan 19 – 22, 2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
- FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mon, Jan 19 – Thu, Jan 22, 2009",
+ assertEquals("Jan 19\u2009\u2013\u200922, 2009", formatDateRange(en_US, tz, fixedTime,
+ fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
+ assertEquals("Mon, Jan 19\u2009\u2013\u2009Thu, Jan 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("Monday, January 19 – Thursday, January 22, 2009",
+ assertEquals("Monday, January 19\u2009\u2013\u2009Thursday, January 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("January 19 – April 22, 2009",
+ assertEquals("January 19\u2009\u2013\u2009April 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("Jan 19 – Apr 22, 2009",
+ assertEquals("Jan 19\u2009\u2013\u2009Apr 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mon, Jan 19 – Wed, Apr 22, 2009",
+ assertEquals("Mon, Jan 19\u2009\u2013\u2009Wed, Apr 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("January – April 2009",
+ assertEquals("January\u2009\u2013\u2009April 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("Jan 19, 2009 – Feb 9, 2012",
+ assertEquals("Jan 19, 2009\u2009\u2013\u2009Feb 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Jan 2009 – Feb 2012",
+ assertEquals("Jan 2009\u2009\u2013\u2009Feb 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("January 19, 2009 – February 9, 2012",
+ assertEquals("January 19, 2009\u2009\u2013\u2009February 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("Monday, January 19, 2009 – Thursday, February 9, 2012",
+ assertEquals("Monday, January 19, 2009\u2009\u2013\u2009Thursday, February 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for de_DE.
@@ -225,26 +226,26 @@
assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19. Januar – 22. April 2009",
+ assertEquals("19. Januar\u2009\u2013\u200922. April 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19. Jan. – 22. Apr. 2009",
+ assertEquals("19. Jan.\u2009\u2013\u200922. Apr. 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mo., 19. Jan. – Mi., 22. Apr. 2009",
+ assertEquals("Mo., 19. Jan.\u2009\u2013\u2009Mi., 22. Apr. 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("Januar–April 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19. Jan. 2009 – 9. Feb. 2012",
+ assertEquals("19. Jan. 2009\u2009\u2013\u20099. Feb. 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Jan. 2009 – Feb. 2012",
+ assertEquals("Jan. 2009\u2009\u2013\u2009Feb. 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19. Januar 2009 – 9. Februar 2012",
+ assertEquals("19. Januar 2009\u2009\u2013\u20099. Februar 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("Montag, 19. Januar 2009 – Donnerstag, 9. Februar 2012",
+ assertEquals("Montag, 19. Januar 2009\u2009\u2013\u2009Donnerstag, 9. Februar 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for es_US.
@@ -254,32 +255,32 @@
assertEquals("19–22 de ene de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 de ene – jue, 22 de ene de 2009",
+ assertEquals("lun, 19 de ene\u2009\u2013\u2009jue, 22 de ene de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+ assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19 de enero – 22 de abril de 2009",
+ assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19 de ene – 22 de abr 2009",
+ assertEquals("19 de ene\u2009\u2013\u200922 de abr 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 de ene – mié, 22 de abr de 2009",
+ assertEquals("lun, 19 de ene\u2009\u2013\u2009mié, 22 de abr de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("enero–abril de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19 de ene de 2009 – 9 de feb de 2012",
+ assertEquals("19 de ene de 2009\u2009\u2013\u20099 de feb de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("ene de 2009 – feb de 2012",
+ assertEquals("ene de 2009\u2009\u2013\u2009feb de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+ assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+ assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for es_ES.
@@ -288,32 +289,32 @@
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0));
assertEquals("19–22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 ene – jue, 22 ene 2009",
+ assertEquals("lun, 19 ene\u2009\u2013\u2009jue, 22 ene 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+ assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19 de enero – 22 de abril de 2009",
+ assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19 ene – 22 abr 2009",
+ assertEquals("19 ene\u2009\u2013\u200922 abr 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 ene – mié, 22 abr 2009",
+ assertEquals("lun, 19 ene\u2009\u2013\u2009mié, 22 abr 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("enero–abril de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19 ene 2009 – 9 feb 2012",
+ assertEquals("19 ene 2009\u2009\u2013\u20099 feb 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("ene 2009 – feb 2012",
+ assertEquals("ene 2009\u2009\u2013\u2009feb 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+ assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+ assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
}
@@ -330,7 +331,7 @@
c.set(2046, Calendar.OCTOBER, 4, 3, 30);
long oct_4_2046 = c.getTimeInMillis();
int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL;
- assertEquals("Jan 19, 2042 – Oct 4, 2046",
+ assertEquals("Jan 19, 2042\u2009\u2013\u2009Oct 4, 2046",
formatDateRange(l, tz, jan_19_2042, oct_4_2046, flags));
}
@@ -343,15 +344,15 @@
int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL | FORMAT_SHOW_TIME | FORMAT_24HOUR;
// The Unix epoch is UTC, so 0 is 1970-01-01T00:00Z...
- assertEquals("Jan 1, 1970, 00:00 – Jan 2, 1970, 00:00",
+ assertEquals("Jan 1, 1970, 00:00\u2009\u2013\u2009Jan 2, 1970, 00:00",
formatDateRange(l, utc, 0, DAY + 1, flags));
// But MTV is hours behind, so 0 was still the afternoon of the previous day...
- assertEquals("Dec 31, 1969, 16:00 – Jan 1, 1970, 16:00",
+ assertEquals("Dec 31, 1969, 16:00\u2009\u2013\u2009Jan 1, 1970, 16:00",
formatDateRange(l, pacific, 0, DAY, flags));
}
// http://b/10318326 - we can drop the minutes in a 12-hour time if they're zero,
- // but not if we're using the 24-hour clock. That is: "4 PM" is reasonable, "16" is not.
+ // but not if we're using the 24-hour clock. That is: "4\u202fPM" is reasonable, "16" is not.
@Test
public void test10318326() throws Exception {
long midnight = 0;
@@ -367,23 +368,26 @@
// Full length on-the-hour times.
assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, time24));
- assertEquals("12:00 AM", formatDateRange(l, utc, midnight, midnight, time12));
+ assertEquals("12:00\u202fAM", formatDateRange(l, utc, midnight, midnight, time12));
assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, time24));
- assertEquals("4:00 PM", formatDateRange(l, utc, teaTime, teaTime, time12));
+ assertEquals("4:00\u202fPM", formatDateRange(l, utc, teaTime, teaTime, time12));
// Abbreviated on-the-hour times.
assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, abbr24));
- assertEquals("12 AM", formatDateRange(l, utc, midnight, midnight, abbr12));
+ assertEquals("12\u202fAM", formatDateRange(l, utc, midnight, midnight, abbr12));
assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, abbr24));
- assertEquals("4 PM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
+ assertEquals("4\u202fPM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
// Abbreviated on-the-hour ranges.
- assertEquals("00:00 – 16:00", formatDateRange(l, utc, midnight, teaTime, abbr24));
- assertEquals("12 AM – 4 PM", formatDateRange(l, utc, midnight, teaTime, abbr12));
+ assertEquals("00:00\u2009\u2013\u200916:00", formatDateRange(l, utc, midnight, teaTime,
+ abbr24));
+ assertEquals("12\u202fAM\u2009\u2013\u20094\u202fPM", formatDateRange(l, utc, midnight,
+ teaTime, abbr12));
// Abbreviated mixed ranges.
- assertEquals("00:00 – 16:01", formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr24));
- assertEquals("12:00 AM – 4:01 PM",
+ assertEquals("00:00\u2009\u2013\u200916:01", formatDateRange(l, utc, midnight,
+ teaTime + MINUTE, abbr24));
+ assertEquals("12:00\u202fAM\u2009\u2013\u20094:01\u202fPM",
formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr12));
}
@@ -406,12 +410,12 @@
// Run one millisecond over, though, and you're into the next day.
long nextMorning = 1 * DAY + 1;
- assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+ assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
formatDateRange(l, utc, midnight, nextMorning, flags));
// But the same reasoning applies for that day.
long nextMidnight = 2 * DAY;
- assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+ assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
formatDateRange(l, utc, midnight, nextMidnight, flags));
}
@@ -424,9 +428,9 @@
int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE;
- assertEquals("January 1, 1970, 22:00 – 00:00",
+ assertEquals("January 1, 1970, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, utc, 22 * HOUR, 24 * HOUR, flags));
- assertEquals("January 1, 1970 at 22:00 – January 2, 1970 at 00:30",
+ assertEquals("January 1, 1970 at 22:00\u2009\u2013\u2009January 2, 1970 at 00:30",
formatDateRange(l, utc, 22 * HOUR, 24 * HOUR + 30 * MINUTE, flags));
}
@@ -443,9 +447,9 @@
c.clear();
c.set(1980, Calendar.JANUARY, 1, 0, 0);
long jan_1_1980 = c.getTimeInMillis();
- assertEquals("January 1, 1980, 22:00 – 00:00",
+ assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, utc, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
- assertEquals("January 1, 1980 at 22:00 – January 2, 1980 at 00:30",
+ assertEquals("January 1, 1980 at 22:00\u2009\u2013\u2009January 2, 1980 at 00:30",
formatDateRange(l, utc, jan_1_1980 + 22 * HOUR,
jan_1_1980 + 24 * HOUR + 30 * MINUTE, flags));
}
@@ -463,12 +467,12 @@
c.clear();
c.set(1980, Calendar.JANUARY, 1, 0, 0);
long jan_1_1980 = c.getTimeInMillis();
- assertEquals("January 1, 1980, 22:00 – 00:00",
+ assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, pacific, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
c.set(1980, Calendar.JULY, 1, 0, 0);
long jul_1_1980 = c.getTimeInMillis();
- assertEquals("July 1, 1980, 22:00 – 00:00",
+ assertEquals("July 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, pacific, jul_1_1980 + 22 * HOUR, jul_1_1980 + 24 * HOUR, flags));
}
@@ -531,11 +535,13 @@
formatDateRange(l, utc, oldYear, oldYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
// ...or the start and end years aren't the same...
- assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+ assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+ c.get(Calendar.YEAR)),
formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE));
// (And you can't avoid that --- icu4c steps in and overrides you.)
- assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+ assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+ c.get(Calendar.YEAR)),
formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
}
@@ -595,7 +601,7 @@
formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags));
assertEquals("يونۍ د ۱۹۸۰ د فبروري ۱۰",
formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags));
- assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ ค.ศ. 1980",
+ assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ 1980",
formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags));
}
@@ -607,9 +613,12 @@
int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR;
- assertEquals("10 – 11 AM", formatDateRange(l, utc, 10 * HOUR, 11 * HOUR, flags));
- assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11 * HOUR, 13 * HOUR, flags));
- assertEquals("2 – 3 PM", formatDateRange(l, utc, 14 * HOUR, 15 * HOUR, flags));
+ assertEquals("10\u2009\u2013\u200911\u202fAM", formatDateRange(l, utc,
+ 10 * HOUR, 11 * HOUR, flags));
+ assertEquals("11\u202fAM\u2009\u2013\u20091\u202fPM", formatDateRange(l, utc,
+ 11 * HOUR, 13 * HOUR, flags));
+ assertEquals("2\u2009\u2013\u20093\u202fPM", formatDateRange(l, utc,
+ 14 * HOUR, 15 * HOUR, flags));
}
// http://b/20708022
@@ -618,8 +627,8 @@
final ULocale locale = new ULocale("en");
final TimeZone timeZone = TimeZone.getTimeZone("UTC");
- assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone,
- 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
+ assertEquals("11:00\u202fPM\u2009\u2013\u200912:00\u202fAM", formatDateRange(locale,
+ timeZone, 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
}
// http://b/68847519
@@ -629,23 +638,25 @@
ENGLISH, GMT_ZONE, from, to, FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_24HOUR);
// If we're showing times and the end-point is midnight the following day, we want the
// behaviour of suppressing the date for the end...
- assertEquals("February 27, 2007, 04:00 – 00:00", fmt.apply(1172548800000L, 1172620800000L));
+ assertEquals("February 27, 2007, 04:00\u2009\u2013\u200900:00", fmt.apply(1172548800000L,
+ 1172620800000L));
// ...unless the start-point is also midnight, in which case we need dates to disambiguate.
- assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172534400000L, 1172620800000L));
// We want to show the date if the end-point is a millisecond after midnight the following
// day, or if it is exactly midnight the day after that.
- assertEquals("February 27, 2007 at 04:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172548800000L, 1172620800001L));
- assertEquals("February 27, 2007 at 04:00 – March 1, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009March 1, 2007 at 00:00",
fmt.apply(1172548800000L, 1172707200000L));
// We want to show the date if the start-point is anything less than a minute after
// midnight,
// since that gets displayed as midnight...
- assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172534459999L, 1172620800000L));
// ...but not if it is exactly one minute after midnight.
- assertEquals("February 27, 2007, 00:01 – 00:00", fmt.apply(1172534460000L, 1172620800000L));
+ assertEquals("February 27, 2007, 00:01\u2009\u2013\u200900:00", fmt.apply(1172534460000L,
+ 1172620800000L));
}
// http://b/68847519
@@ -656,16 +667,20 @@
// If we're only showing dates and the end-point is midnight of any day, we want the
// behaviour of showing an end date one earlier. So if the end-point is March 2, 2007 00:00,
// show March 1, 2007 instead (whether the start-point is midnight or not).
- assertEquals("February 27 – March 1, 2007", fmt.apply(1172534400000L, 1172793600000L));
- assertEquals("February 27 – March 1, 2007", fmt.apply(1172548800000L, 1172793600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+ fmt.apply(1172534400000L, 1172793600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+ fmt.apply(1172548800000L, 1172793600000L));
// We want to show the true date if the end-point is a millisecond after midnight.
- assertEquals("February 27 – March 2, 2007", fmt.apply(1172534400000L, 1172793600001L));
+ assertEquals("February 27\u2009\u2013\u2009March 2, 2007",
+ fmt.apply(1172534400000L, 1172793600001L));
// 2006-02-27 00:00:00.000 GMT - 2007-03-02 00:00:00.000 GMT
- assertEquals("February 27, 2006 – March 1, 2007",
+ assertEquals("February 27, 2006\u2009\u2013\u2009March 1, 2007",
fmt.apply(1140998400000L, 1172793600000L));
// Spans a leap year's Feb 29th.
- assertEquals("February 27 – March 1, 2004", fmt.apply(1077840000000L, 1078185600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2004",
+ fmt.apply(1077840000000L, 1078185600000L));
}
}
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c051..39ed82ef 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -139,16 +139,16 @@
fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
final long hourDuration = 2 * 60 * 60 * 1000;
- assertEquals("5:30:15 AM Greenwich Mean Time", DateUtils.formatSameDayTime(
+ assertEquals("5:30:15\u202fAM Greenwich Mean Time", DateUtils.formatSameDayTime(
fixedTime + hourDuration, fixedTime, java.text.DateFormat.FULL,
java.text.DateFormat.FULL));
- assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
- assertEquals("5:30:15 AM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
- assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
- assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
}
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index b342516..2337802 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -468,37 +468,37 @@
cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
final long base = cal.getTimeInMillis();
- assertEquals("5 seconds ago, 10:49 AM",
+ assertEquals("5 seconds ago, 10:49\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
MINUTE_IN_MILLIS, 0));
- assertEquals("5 min. ago, 10:45 AM",
+ assertEquals("5 min. ago, 10:45\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("0 hr. ago, 10:45 AM",
+ assertEquals("0 hr. ago, 10:45\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base,
HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("5 hours ago, 5:50 AM",
+ assertEquals("5 hours ago, 5:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base,
HOUR_IN_MILLIS, DAY_IN_MILLIS, 0));
- assertEquals("Yesterday, 7:50 PM",
+ assertEquals("Yesterday, 7:50\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("5 days ago, 10:50 AM",
+ assertEquals("5 days ago, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM",
+ assertEquals("Jan 29, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
YEAR_IN_MILLIS, 0));
// User-supplied flags should be ignored when formatting the date clause.
final int FORMAT_SHOW_WEEKDAY = 0x00002;
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
WEEK_IN_MILLIS,
FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY));
@@ -514,14 +514,14 @@
// So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'.
cal.set(2014, Calendar.MARCH, 9, 3, 15, 0);
long base = cal.getTimeInMillis();
- assertEquals("Yesterday, 9:15 PM",
+ assertEquals("Yesterday, 9:15\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
// 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'.
cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
base = cal.getTimeInMillis();
- assertEquals("In 1 hour, 4:00 AM",
+ assertEquals("In 1 hour, 4:00\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
@@ -529,22 +529,22 @@
// 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'.
cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0);
base = cal.getTimeInMillis();
- assertEquals("Yesterday, 10:20 PM",
+ assertEquals("Yesterday, 10:20\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0);
base = cal.getTimeInMillis();
// 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'.
- assertEquals("In 45 minutes, 1:30 AM",
+ assertEquals("In 45 minutes, 1:30\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
// 45 minutes later, it should be 'In 45 minutes, 1:15 AM'.
- assertEquals("In 45 minutes, 1:15 AM",
+ assertEquals("In 45 minutes, 1:15\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS,
base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
// Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'.
- assertEquals("In 45 minutes, 2:00 AM",
+ assertEquals("In 45 minutes, 2:00\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS,
base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
}
@@ -593,7 +593,7 @@
Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US);
yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0);
long yesterday1 = yesterdayCalendar1.getTimeInMillis();
- assertEquals("Yesterday, 10:24 AM",
+ assertEquals("Yesterday, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -601,7 +601,7 @@
Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US);
yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0);
long yesterday2 = yesterdayCalendar2.getTimeInMillis();
- assertEquals("Yesterday, 10:22 AM",
+ assertEquals("Yesterday, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -609,7 +609,7 @@
Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US);
twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0);
long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis();
- assertEquals("2 days ago, 10:24 AM",
+ assertEquals("2 days ago, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -617,7 +617,7 @@
Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US);
twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0);
long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis();
- assertEquals("2 days ago, 10:22 AM",
+ assertEquals("2 days ago, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -625,7 +625,7 @@
Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US);
tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0);
long tomorrow1 = tomorrowCalendar1.getTimeInMillis();
- assertEquals("Tomorrow, 10:22 AM",
+ assertEquals("Tomorrow, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -633,7 +633,7 @@
Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US);
tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0);
long tomorrow2 = tomorrowCalendar2.getTimeInMillis();
- assertEquals("Tomorrow, 10:24 AM",
+ assertEquals("Tomorrow, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -641,7 +641,7 @@
Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US);
twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0);
long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis();
- assertEquals("In 2 days, 10:22 AM",
+ assertEquals("In 2 days, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -649,7 +649,7 @@
Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US);
twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0);
long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis();
- assertEquals("In 2 days, 10:24 AM",
+ assertEquals("In 2 days, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
}
@@ -664,11 +664,11 @@
cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0);
long base = cal.getTimeInMillis();
- assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("11/27/2011, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
@@ -687,11 +687,11 @@
// Feb 5, 2018 at 10:50 PST
cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0);
base = cal.getTimeInMillis();
- assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("11/27/2017, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 44bb062..0bf133f 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -24,6 +24,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -98,14 +99,14 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// test if setVisibility can show IME
mImeConsumer.onWindowFocusGained(true);
- mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
// test if setVisibility can hide IME
- mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
});
}
@@ -117,7 +118,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained(true);
- mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
// set control and verify visibility is applied.
InsetsSourceControl control =
@@ -125,9 +126,11 @@
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
- eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */);
+ eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
+ any() /* statsToken */);
verify(mController, never()).applyAnimation(
- eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */);
+ eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */,
+ any() /* statsToken */);
});
}
@@ -153,7 +156,8 @@
mImeConsumer.onWindowFocusGained(hasWindowFocus);
final boolean imeVisible = hasWindowFocus && hasViewFocus;
if (imeVisible) {
- mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+ null /* statsToken */);
}
// set control and verify visibility is applied.
@@ -169,20 +173,21 @@
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(false) /* fromIme */,
- eq(expectSkipAnim) /* skipAnim */);
+ eq(expectSkipAnim) /* skipAnim */, null /* statsToken */);
}
// If previously hasViewFocus is false, verify when requesting the IME visible next
// time will not skip animation.
if (!hasViewFocus) {
- mController.show(WindowInsets.Type.ime(), true);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+ null /* statsToken */);
mController.onControlsChanged(new InsetsSourceControl[]{ control });
// Verify IME show animation should be triggered when control becomes available and
// the animation will be skipped by getAndClearSkipAnimationOnce invoked.
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(true) /* fromIme */,
- eq(false) /* skipAnim */);
+ eq(false) /* skipAnim */, null /* statsToken */);
}
});
}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index d0f7fe04..c88255e 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -27,7 +27,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -90,7 +89,6 @@
mInsetsState = new InsetsState();
mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100));
mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
- doNothing().when(mMockController).onRequestedVisibilityChanged(any());
InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState,
() -> mMockTransaction, mMockController);
topConsumer.setControl(
@@ -111,7 +109,8 @@
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
mMockController, 10 /* durationMs */, new LinearInterpolator(),
- 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */);
+ 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+ null /* statsToken */);
mController.setReadyDispatched(true);
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 5e12313..c6fa778 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -248,35 +248,29 @@
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
verify(loggingListener).onReady(notNull(), anyInt());
});
}
@Test
public void testAnimationEndState() {
- InsetsSourceControl[] controls = prepareControls();
- InsetsSourceControl navBar = controls[0];
- InsetsSourceControl statusBar = controls[1];
- InsetsSourceControl ime = controls[2];
+ prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.show(all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ final @InsetsType int types = navigationBars() | statusBars() | ime();
+ assertEquals(types, mController.getRequestedVisibleTypes() & types);
- mController.hide(ime(), true /* fromIme */);
+ mController.hide(ime(), true /* fromIme */, null /* statsToken */);
mController.hide(all());
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(0, mController.getRequestedVisibleTypes() & types);
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -288,12 +282,12 @@
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
- mController.show(ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(ime(), true /* fromIme */);
+ assertTrue(isRequestedVisible(mController, ime()));
+ mController.hide(ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertFalse(isRequestedVisible(mController, ime()));
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -307,26 +301,22 @@
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = navigationBars() | systemBars();
+ int types = navigationBars() | statusBars();
// test hide select types.
mController.hide(types);
- assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
- assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
- assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
- assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(navigationBars()));
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(statusBars()));
+ assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
- // test hide all
+ // test show all
mController.show(types);
- assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
- assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(navigationBars()));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(types, mController.getRequestedVisibleTypes() & (types | ime()));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -339,33 +329,27 @@
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = navigationBars() | systemBars();
+ int types = navigationBars() | statusBars();
// test show select types.
mController.show(types);
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(types, mController.getRequestedVisibleTypes() & types);
+ assertEquals(0, mController.getRequestedVisibleTypes() & ime());
// test hide all
mController.hide(all());
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
// test single show
mController.show(navigationBars());
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(navigationBars(),
+ mController.getRequestedVisibleTypes() & (types | ime()));
// test single hide
mController.hide(navigationBars());
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -373,49 +357,38 @@
@Test
public void testShowHideMultiple() {
- InsetsSourceControl[] controls = prepareControls();
- InsetsSourceControl navBar = controls[0];
- InsetsSourceControl statusBar = controls[1];
- InsetsSourceControl ime = controls[2];
+ prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// start two animations and see if previous is cancelled and final state is reached.
mController.hide(navigationBars());
mController.hide(systemBars());
- assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
- assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+ int types = navigationBars() | statusBars();
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
mController.show(navigationBars());
mController.show(systemBars());
- assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
- assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(navigationBars()));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(types, mController.getRequestedVisibleTypes() & (types | ime()));
- int types = navigationBars() | systemBars();
// show two at a time and hide one by one.
mController.show(types);
mController.hide(navigationBars());
- assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
- assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(statusBars(), mController.getRequestedVisibleTypes() & (types | ime()));
mController.hide(systemBars());
- assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
- assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(navigationBars()));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -428,20 +401,16 @@
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = navigationBars() | systemBars();
+ int types = navigationBars() | statusBars();
// show two at a time and hide one by one.
mController.show(types);
mController.hide(navigationBars());
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(statusBars(), mController.getRequestedVisibleTypes() & (types | ime()));
mController.hide(systemBars());
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+ assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -453,7 +422,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.hide(statusBars());
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+ assertFalse(isRequestedVisible(mController, statusBars()));
assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
// Loosing control
@@ -461,14 +430,14 @@
state.setSourceVisible(ITYPE_STATUS_BAR, true);
mController.onStateChanged(state);
mController.onControlsChanged(new InsetsSourceControl[0]);
- assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+ assertFalse(isRequestedVisible(mController, statusBars()));
assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
// Gaining control
mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
- assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
- assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+ assertFalse(isRequestedVisible(mController, statusBars()));
assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -483,14 +452,14 @@
assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
// Gaining control shortly after
mController.onControlsChanged(createSingletonControl(ITYPE_IME));
- assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
+ assertTrue(isRequestedVisible(mController, ime()));
assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -507,11 +476,11 @@
mController.onControlsChanged(createSingletonControl(ITYPE_IME));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
- assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
- assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
+ assertTrue(isRequestedVisible(mController, ime()));
assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -538,7 +507,7 @@
});
waitUntilNextFrame();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+ assertFalse(isRequestedVisible(mController, statusBars()));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -565,7 +534,7 @@
});
waitUntilNextFrame();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+ assertFalse(isRequestedVisible(mController, statusBars()));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -585,7 +554,7 @@
verify(listener, never()).onReady(any(), anyInt());
// Pretend that IME is calling.
- mController.show(ime(), true);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -669,7 +638,7 @@
mController.onControlsChanged(createSingletonControl(ITYPE_IME));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3);
@@ -701,6 +670,7 @@
private void doTestResizeAnimation_insetsTypes(@InternalInsetsType int type,
@AnimationType int expectedAnimationType) {
+ final @InsetsType int publicType = InsetsState.toPublicType(type);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
final InsetsState state1 = new InsetsState();
state1.getSource(type).setVisible(true);
@@ -711,15 +681,15 @@
// New insets source won't cause the resize animation.
mController.onStateChanged(state1);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
// Changing frame might cause the resize animation. This depends on the insets type.
mController.onStateChanged(state2);
- assertEquals(message, expectedAnimationType, mController.getAnimationType(type));
+ assertEquals(message, expectedAnimationType, mController.getAnimationType(publicType));
// Cancel the existing animations for the next iteration.
mController.cancelExistingAnimations();
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -728,6 +698,7 @@
public void testResizeAnimation_displayFrame() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+ final @InsetsType int publicType = statusBars();
final InsetsState state1 = new InsetsState();
state1.setDisplayFrame(new Rect(0, 0, 500, 1000));
state1.getSource(type).setFrame(0, 0, 500, 50);
@@ -738,11 +709,11 @@
// New insets source won't cause the resize animation.
mController.onStateChanged(state1);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
// Changing frame won't cause the resize animation if the display frame is also changed.
mController.onStateChanged(state2);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -751,6 +722,7 @@
public void testResizeAnimation_visibility() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+ final @InsetsType int publicType = statusBars();
final InsetsState state1 = new InsetsState();
state1.getSource(type).setVisible(true);
state1.getSource(type).setFrame(0, 0, 500, 50);
@@ -764,17 +736,17 @@
// New insets source won't cause the resize animation.
mController.onStateChanged(state1);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
// Changing source visibility (visible --> invisible) won't cause the resize animation.
// The previous source and the current one must be both visible.
mController.onStateChanged(state2);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
// Changing source visibility (invisible --> visible) won't cause the resize animation.
// The previous source and the current one must be both visible.
mController.onStateChanged(state3);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -873,7 +845,7 @@
// Showing invisible ime should only causes insets change once.
clearInvocations(mTestHost);
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
verify(mTestHost, times(1)).notifyInsetsChanged();
// Sending the same insets state should not cause insets change.
@@ -940,10 +912,10 @@
// Verify IME requested visibility should be updated to IME consumer from controller.
mController.show(ime());
- assertTrue(imeInsetsConsumer.isRequestedVisible());
+ assertTrue(isRequestedVisible(mController, ime()));
mController.hide(ime());
- assertFalse(imeInsetsConsumer.isRequestedVisible());
+ assertFalse(isRequestedVisible(mController, ime()));
});
}
@@ -980,6 +952,10 @@
return controls;
}
+ private static boolean isRequestedVisible(InsetsController controller, @InsetsType int type) {
+ return (controller.getRequestedVisibleTypes() & type) != 0;
+ }
+
public static class TestHost extends ViewRootInsetsControllerHost {
private @InsetsType int mRequestedVisibleTypes = defaultVisible();
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 8cf118c..1253278 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -122,7 +122,6 @@
public void testHide() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mConsumer.hide();
- assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
verify(mSpyInsetsSource).setVisible(eq(false));
});
@@ -134,7 +133,6 @@
// Insets source starts out visible
mConsumer.hide();
mConsumer.show(false /* fromIme */);
- assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
verify(mSpyInsetsSource).setVisible(eq(false));
verify(mSpyInsetsSource).setVisible(eq(true));
});
@@ -240,7 +238,7 @@
// visibility won't be updated when the consumer received the same leash in setControl.
insetsController.controlWindowInsetsAnimation(ime(), 0L,
null /* interpolator */, null /* cancellationSignal */, null /* listener */);
- assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER);
+ assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
verify(mMockTransaction, never()).show(mLeash);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 99670d9..cc02bbb 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 40;
+ private static final int NUM_MARSHALLED_PROPERTIES = 42;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 35d5948..6443ac18 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -27,6 +27,8 @@
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.ScreenCapture;
import java.util.Collections;
import java.util.List;
@@ -180,6 +182,10 @@
public void takeScreenshot(int displayId, RemoteCallback callback) {}
+ public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {}
+
public void setFocusAppearance(int strokeWidth, int color) {}
public void setCacheEnabled(boolean enabled) {}
@@ -217,4 +223,7 @@
public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() throws RemoteException {
return null;
}
+
+ @Override
+ public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {}
}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 00b3693..bbf9f3c 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -128,6 +128,7 @@
RemoteViews clone = child.clone();
}
+ @SuppressWarnings("ReturnValueIgnored")
@Test
public void clone_repeatedly() {
RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -485,6 +486,7 @@
}
}
+ @SuppressWarnings("ReturnValueIgnored")
@Test
public void nestedAddViews() {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -509,6 +511,7 @@
parcelAndRecreate(views);
}
+ @SuppressWarnings("ReturnValueIgnored")
@Test
public void nestedLandscapeViews() {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
index 499f7a5..875cd0b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
@@ -24,11 +24,13 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.util.Pair;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import java.util.List;
+import java.util.function.BiFunction;
import java.util.function.Function;
/**
@@ -50,6 +52,9 @@
public Function<PackageManager, PackageManager> createPackageManager;
public Function<TargetInfo, Boolean> onSafelyStartCallback;
public Function<ChooserListAdapter, Void> onQueryDirectShareTargets;
+ public BiFunction<
+ IChooserWrapper, ChooserListAdapter, Pair<Integer, ChooserActivity.ServiceResultInfo[]>>
+ directShareTargets;
public ResolverListController resolverListController;
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
@@ -72,6 +77,7 @@
public void reset() {
onSafelyStartCallback = null;
onQueryDirectShareTargets = null;
+ directShareTargets = null;
isVoiceInteraction = null;
createPackageManager = null;
previewThumbnail = null;
@@ -112,4 +118,3 @@
private ChooserActivityOverrideData() {}
}
-
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index e8c7ce0..d656678 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -81,6 +81,7 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
+import android.util.Pair;
import android.view.View;
import androidx.annotation.CallSuper;
@@ -89,6 +90,7 @@
import androidx.test.rule.ActivityTestRule;
import com.android.internal.R;
+import com.android.internal.app.ChooserActivity.ServiceResultInfo;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -2166,8 +2168,8 @@
assertThat(logger.numCalls(), is(6));
}
- @Test @Ignore
- public void testDirectTargetLogging() throws InterruptedException {
+ @Test
+ public void testDirectTargetLogging() {
Intent sendIntent = createSendTextIntent();
// We need app targets for direct targets to get displayed
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -2187,30 +2189,35 @@
resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ ChooserActivityOverrideData
+ .getInstance()
+ .directShareTargets = (activity, adapter) -> {
+ DisplayResolveInfo displayInfo = activity.createTestDisplayResolveInfo(
+ sendIntent,
+ ri,
+ "testLabel",
+ "testInfo",
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null);
+ ServiceResultInfo[] results = {
+ new ServiceResultInfo(
+ displayInfo,
+ serviceTargets,
+ adapter.getUserHandle())};
+ // TODO: consider covering the other type.
+ // Only 2 types are expected out of the shortcut loading logic:
+ // - TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER, if shortcuts were loaded from
+ // the ShortcutManager, and;
+ // - TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE, if shortcuts were loaded
+ // from AppPredictor.
+ // Ideally, our tests should cover all of them.
+ return new Pair<>(TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER, results);
+ };
+
// Start activity
final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- // Insert the direct share target
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
- directShareToShortcutInfos.put(serviceTargets.get(0), null);
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> activity.getAdapter().addServiceResults(
- activity.createTestDisplayResolveInfo(sendIntent,
- ri,
- "testLabel",
- "testInfo",
- sendIntent,
- /* resolveInfoPresentationGetter */ null),
- serviceTargets,
- TARGET_TYPE_CHOOSER_TARGET,
- directShareToShortcutInfos)
- );
- // Thread.sleep shouldn't be a thing in an integration test but it's
- // necessary here because of the way the code is structured
- // TODO: restructure the tests b/129870719
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
-
assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
activity.getAdapter().getCount(), is(3));
assertThat("Chooser should have exactly one selectable direct target",
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 7f85982..4c3235c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -31,6 +31,7 @@
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.UserHandle;
+import android.util.Pair;
import android.util.Size;
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
@@ -239,6 +240,12 @@
@Override
protected void queryDirectShareTargets(ChooserListAdapter adapter,
boolean skipAppPredictionService) {
+ if (sOverrides.directShareTargets != null) {
+ Pair<Integer, ServiceResultInfo[]> result =
+ sOverrides.directShareTargets.apply(this, adapter);
+ sendShortcutManagerShareTargetResults(result.first, result.second);
+ return;
+ }
if (sOverrides.onQueryDirectShareTargets != null) {
sOverrides.onQueryDirectShareTargets.apply(adapter);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 82b2bf4..8207c9e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -1054,10 +1054,23 @@
super(new Injector() {
public Random getRandomGenerator() {
return new Random() {
- int mCallCount = 0;
+ int mCallCount = -1;
public int nextInt() {
- return mCallCount++;
+ throw new IllegalStateException("Should not use nextInt()");
+ }
+
+ public int nextInt(int x) {
+ if (mCallCount == -1) {
+ // The tests are written such that they expect
+ // the first call to nextInt() to be on the first
+ // callEnded(). However, the BinderCallsStats
+ // constructor also calls nextInt(). Fake 0 being
+ // rolled twice.
+ mCallCount++;
+ return 0;
+ }
+ return (mCallCount++) % x;
}
};
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 5af7376..7bd53b9 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -98,7 +98,7 @@
assertEquals(1, latencyHistograms.size());
LatencyDims dims = latencyHistograms.keySet().iterator().next();
assertEquals(binder.getClass(), dims.getBinderClass());
- assertEquals(1, dims.getTransactionCode());
+ assertEquals(2, dims.getTransactionCode()); // the first nextInt() is in the constructor
assertThat(latencyHistograms.get(dims)).asList().containsExactly(1, 0, 0, 0, 0).inOrder();
}
@@ -313,11 +313,11 @@
int mCallCount = 0;
public int nextInt() {
- return mCallCount++;
+ throw new IllegalStateException("Should not use nextInt()");
}
public int nextInt(int x) {
- return 1;
+ return (mCallCount++) % x;
}
};
}
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index b34554c..c3d40eb 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.FileUtils;
+import android.util.IntArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -33,13 +34,15 @@
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.Arrays;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ProcLocksReaderTest implements
ProcLocksReader.ProcLocksReaderCallback {
private File mProcDirectory;
- private ArrayList<Integer> mPids = new ArrayList<>();
+
+ private ArrayList<int[]> mPids = new ArrayList<>();
@Before
public void setUp() {
@@ -54,41 +57,51 @@
@Test
public void testRunSimpleLocks() throws Exception {
- String simpleLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
+ String simpleLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
runHandleBlockingFileLocks(simpleLocks);
assertTrue(mPids.isEmpty());
}
@Test
public void testRunBlockingLocks() throws Exception {
- String blockedLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
- "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
- "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n"
+ + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n"
+ + "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
runHandleBlockingFileLocks(blockedLocks);
- assertTrue(mPids.remove(0).equals(18292));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+ assertTrue(mPids.isEmpty());
+ }
+
+ @Test
+ public void testRunLastBlockingLocks() throws Exception {
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n";
+ runHandleBlockingFileLocks(blockedLocks);
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
assertTrue(mPids.isEmpty());
}
@Test
public void testRunMultipleBlockingLocks() throws Exception {
- String blockedLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
- "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
- "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n" +
- "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n" +
- "5: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n"
+ + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n"
+ + "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n"
+ + "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n"
+ + "5: FLOCK ADVISORY READ 3888 fd:09:14230 0 EOF\n"
+ + "5: -> FLOCK ADVISORY READ 3887 fd:09:14230 0 EOF\n";
runHandleBlockingFileLocks(blockedLocks);
- assertTrue(mPids.remove(0).equals(18292));
- assertTrue(mPids.remove(0).equals(3840));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{3840, 3841}));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{3888, 3887}));
assertTrue(mPids.isEmpty());
}
@@ -102,11 +115,12 @@
/**
* Call the callback function of handleBlockingFileLocks().
- *
- * @param pid Each process that hold file locks blocking other processes.
+ * @param pids Each process that hold file locks blocking other processes.
+ * pids[0] is the process blocking others
+ * pids[1..n-1] are the processes being blocked
*/
@Override
- public void onBlockingFileLock(int pid) {
- mPids.add(pid);
+ public void onBlockingFileLock(IntArray pids) {
+ mPids.add(pids.toArray());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
new file mode 100644
index 0000000..c540a15
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.internal.os;
+
+import static org.junit.Assert.assertThrows;
+
+import android.compat.testing.PlatformCompatChangeRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Test SafeZipPathCallback.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SafeZipPathValidatorCallbackTest {
+ @Rule
+ public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
+
+ @Before
+ public void setUp() {
+ RuntimeInit.initZipPathValidatorCallback();
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+ throws Exception {
+ final String[] dangerousEntryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ };
+ for (String entryName : dangerousEntryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+
+ assertThrows(
+ "ZipException expected for entry: " + entryName,
+ ZipException.class,
+ () -> {
+ new ZipFile(tempFile);
+ });
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+ throws Exception {
+ final String[] dangerousEntryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ };
+ for (String entryName : dangerousEntryNames) {
+ byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) {
+ assertThrows(
+ "ZipException expected for entry: " + entryName,
+ ZipException.class,
+ () -> {
+ zis.getNextEntry();
+ });
+ }
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+ throws Exception {
+ final String[] normalEntryNames = {
+ "foo", "foo.bar", "foo..bar",
+ };
+ for (String entryName : normalEntryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+ try {
+ new ZipFile((tempFile));
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+ throws Exception {
+ final String[] normalEntryNames = {
+ "foo", "foo.bar", "foo..bar",
+ };
+ for (String entryName : normalEntryNames) {
+ byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try {
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+ zis.getNextEntry();
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+ throws Exception {
+ final String[] entryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ "foo",
+ "foo.bar",
+ "foo..bar",
+ };
+ for (String entryName : entryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+ try {
+ new ZipFile((tempFile));
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+ throws Exception {
+ final String[] entryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ "foo",
+ "foo.bar",
+ "foo..bar",
+ };
+ for (String entryName : entryNames) {
+ byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try {
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+ zis.getNextEntry();
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ }
+ }
+
+ private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName)
+ throws IOException {
+ FileOutputStream tempFileStream = new FileOutputStream(tempFile);
+ writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName);
+ tempFileStream.close();
+ }
+
+ private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName)
+ throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ writeZipOutputStreamWithEmptyEntry(bos, entryName);
+ return bos.toByteArray();
+ }
+
+ private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName)
+ throws IOException {
+ ZipOutputStream zos = new ZipOutputStream(os);
+ ZipEntry entry = new ZipEntry(entryName);
+ zos.putNextEntry(entry);
+ zos.write(new byte[2]);
+ zos.closeEntry();
+ zos.close();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
index 6c50bce..8b30828 100644
--- a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
@@ -16,6 +16,8 @@
package com.android.internal.util;
+import static org.junit.Assert.assertThrows;
+
import android.os.SystemClock;
import android.text.format.DateUtils;
@@ -170,10 +172,9 @@
}
void assertThrow(Fn fn) {
- try {
+ assertThrows(Throwable.class, () -> {
fn.call();
- fail("expected n exception to be thrown.");
- } catch (Throwable t) { }
+ });
}
interface Fn { void call(); }
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 9e39e13..3e3c77b 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -377,6 +377,11 @@
notifyDeviceStateInfoChanged();
}
+ // No-op in the test since DeviceStateManagerGlobal just calls into the system server with
+ // no business logic around it.
+ @Override
+ public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {}
+
public void setSupportedStates(int[] states) {
mSupportedStates = states;
notifyDeviceStateInfoChanged();
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
index 41b8956f..a226325 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
@@ -31,6 +31,7 @@
assertEquals(3366, getActivity().getValue());
}
+ @SuppressWarnings("ReturnValueIgnored")
public void testAnnotation() throws Exception {
assertEquals(ReferencedByAnnotation.B,
((AnnotationWithEnum) TestApplication.annotation).value());
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
index 3465989..2da9a2e 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
@@ -45,7 +44,6 @@
import org.junit.runners.JUnit4;
import java.util.Collections;
-import java.util.List;
import java.util.concurrent.TimeoutException;
@RunWith(JUnit4.class)
@@ -221,11 +219,56 @@
}
@Test
+ public void setResourceValue_withNullResourceName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(NullPointerException.class,
+ () -> builder.setResourceValue(null, TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyResourceName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyPackageName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue(":color/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withInvalidTypeName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("c/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyTypeName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
public void testInvalidResourceValues() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
"android", TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
- .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+ .setResourceValue("color/something", TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
diff --git a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
index c53f4cc..1581abb 100644
--- a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import java.util.ArrayList;
+import java.util.Objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -39,11 +40,11 @@
Integer argValue;
private void addNotifyCount(Integer callback) {
- if (callback == callback1) {
+ if (Objects.equals(callback, callback1)) {
notify1++;
- } else if (callback == callback2) {
+ } else if (Objects.equals(callback, callback2)) {
notify2++;
- } else if (callback == callback3) {
+ } else if (Objects.equals(callback, callback3)) {
notify3++;
}
deepNotifyCount[callback]++;
@@ -114,7 +115,7 @@
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
int arg1, Integer arg) {
addNotifyCount(callback);
- if (callback == callback1) {
+ if (Objects.equals(callback, callback1)) {
registry.remove(callback1);
registry.remove(callback2);
}
@@ -166,9 +167,9 @@
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
int arg1, Integer arg) {
addNotifyCount(callback);
- if (callback == callback1) {
+ if (Objects.equals(callback, callback1)) {
registry.remove(callback2);
- } else if (callback == callback3) {
+ } else if (Objects.equals(callback, callback3)) {
registry.add(callback2);
}
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 9a1b8a9..6328b02 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -288,6 +288,15 @@
targetSdk="33">
<new-permission name="android.permission.READ_MEDIA_IMAGES" />
</split-permission>
+ <split-permission name="android.permission.READ_MEDIA_IMAGES">
+ <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+ </split-permission>
+ <split-permission name="android.permission.READ_MEDIA_VIDEO">
+ <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+ </split-permission>
+ <split-permission name="android.permission.ACCESS_MEDIA_LOCATION">
+ <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+ </split-permission>
<!-- This is a list of all the libraries available for application
code to link against. -->
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index decfb9f..3e2b71f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -495,6 +495,10 @@
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<!-- Permission required for CTS test - CtsTelephonyTestCases -->
<permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+ <!-- Permission required for CTS test - CtsAppTestCases -->
+ <permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+ <permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+ <permission name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 85c6beb..4cc06e3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1357,6 +1357,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "-787664727": {
+ "message": "Cannot launch dream activity due to invalid state. dream component: %s packageName: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_DREAM",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"-784959154": {
"message": "Attempted to add private presentation window to a non-private display. Aborting.",
"level": "WARN",
@@ -2173,12 +2179,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
- "-23020844": {
- "message": "Back: Reset surfaces",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/BackNavigationController.java"
- },
"-21399771": {
"message": "activity %s already destroying, skipping request with reason:%s",
"level": "VERBOSE",
@@ -2893,6 +2893,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "601283564": {
+ "message": "Dream packageName does not match active dream. Package %s does not match %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_DREAM",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"608694300": {
"message": " NEW SURFACE SESSION %s",
"level": "INFO",
@@ -3133,12 +3139,6 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
- "829869827": {
- "message": "Cannot launch dream activity due to invalid state. dreaming: %b packageName: %s",
- "level": "ERROR",
- "group": "WM_DEBUG_DREAM",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"835814848": {
"message": "%s",
"level": "INFO",
@@ -3817,12 +3817,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1544805551": {
- "message": "Skipping app transition animation. task=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1557732761": {
"message": "For Intent %s bringing to top: %s",
"level": "DEBUG",
@@ -4159,12 +4153,6 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
- "1918771553": {
- "message": "Dream packageName does not match active dream. Package %s does not match %s or %s",
- "level": "ERROR",
- "group": "WM_DEBUG_DREAM",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"1921821199": {
"message": "Preserving %s until the new one is added",
"level": "VERBOSE",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index af96c74..215b60e 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -371,6 +371,7 @@
# key 413 "KEY_DIGITS"
# key 414 "KEY_TEEN"
# key 415 "KEY_TWEN"
+key 528 FOCUS
key 429 CONTACTS
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
index 8706a68..42e3046 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
@@ -168,6 +168,7 @@
*/
private static final Matcher<ExpressionTree> CONVERT_PRIMITIVE_TO_STRING =
new Matcher<ExpressionTree>() {
+ @SuppressWarnings("TreeToString") //TODO: Fix me
@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
if (PRIMITIVE_TO_STRING.matches(tree, state)) {
@@ -205,6 +206,7 @@
*/
private static final Matcher<ExpressionTree> CONVERT_STRING_TO_PRIMITIVE =
new Matcher<ExpressionTree>() {
+ @SuppressWarnings("TreeToString") //TODO: Fix me
@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
if (PRIMITIVE_PARSE.matches(tree, state)) {
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index abf7e99..42c892a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1667,6 +1667,9 @@
* effectively treating them as zeros. In API level {@value Build.VERSION_CODES#P} and above
* these parameters will be respected.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param bitmap The bitmap to draw using the mesh
* @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
* @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
@@ -1678,7 +1681,7 @@
* null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
* in the array.
* @param colorOffset Number of color elements to skip before drawing
- * @param paint May be null. The paint used to draw the bitmap
+ * @param paint May be null. The paint used to draw the bitmap. Antialiasing is not supported.
*/
public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
@NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@@ -1832,9 +1835,12 @@
/**
* Draws the specified bitmap as an N-patch (most often, a 9-patch.)
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
- * @param paint The paint to draw the bitmap with. may be null
+ * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
super.drawPatch(patch, dst, paint);
@@ -1843,9 +1849,12 @@
/**
* Draws the specified bitmap as an N-patch (most often, a 9-patch.)
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
- * @param paint The paint to draw the bitmap with. may be null
+ * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
super.drawPatch(patch, dst, paint);
@@ -2278,6 +2287,9 @@
* array is optional, but if it is present, then it is used to specify the index of each
* triangle, rather than just walking through the arrays in order.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param mode How to interpret the array of vertices
* @param vertexCount The number of values in the vertices array (and corresponding texs and
* colors arrays if non-null). Each logical vertex is two values (x, y), vertexCount
@@ -2292,8 +2304,9 @@
* @param colorOffset Number of values in colors to skip before drawing.
* @param indices If not null, array of indices to reference into the vertex (texs, colors)
* array.
- * @param indexCount number of entries in the indices array (if not null).
- * @param paint Specifies the shader to use if the texs array is non-null.
+ * @param indexCount Number of entries in the indices array (if not null).
+ * @param paint Specifies the shader to use if the texs array is non-null. Antialiasing is not
+ * supported.
*/
public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 48dd3e6..a7fb742 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -982,6 +982,15 @@
}
/**
+ * Notifies the hardware renderer about pending choreographer callbacks.
+ *
+ * @hide
+ */
+ public void notifyCallbackPending() {
+ nNotifyCallbackPending(mNativeProxy);
+ }
+
+ /**
* b/68769804, b/66945974: For low FPS experiments.
*
* @hide
@@ -1536,4 +1545,6 @@
private static native boolean nIsDrawingEnabled();
private static native void nSetRtAnimationsEnabled(boolean rtAnimationsEnabled);
+
+ private static native void nNotifyCallbackPending(long nativeProxy);
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1a80ab3..f0e496f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -137,6 +137,13 @@
* <p>Enabling this flag will cause all draw operations that support
* antialiasing to use it.</p>
*
+ * <p>Notable draw operations that do <b>not</b> support antialiasing include:</p>
+ * <ul>
+ * <li>{@link android.graphics.Canvas#drawBitmapMesh}</li>
+ * <li>{@link android.graphics.Canvas#drawPatch}</li>
+ * <li>{@link android.graphics.Canvas#drawVertices}</li>
+ * </ul>
+ *
* @see #Paint(int)
* @see #setFlags(int)
*/
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 54c9f62..1a878df 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -766,7 +766,7 @@
if (mBackground != null) {
mBackground.onHotspotBoundsChanged();
}
- float newRadius = Math.round(getComputedRadius());
+ float newRadius = getComputedRadius();
for (int i = 0; i < mRunningAnimations.size(); i++) {
RippleAnimationSession s = mRunningAnimations.get(i);
s.setRadius(newRadius);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 4065bd1..e25ee90 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -60,7 +60,7 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Map;
import java.util.Stack;
/**
@@ -1171,18 +1171,14 @@
private static final int NATIVE_ALLOCATION_SIZE = 100;
- private static final HashMap<String, Integer> sPropertyIndexMap =
- new HashMap<String, Integer>() {
- {
- put("translateX", TRANSLATE_X_INDEX);
- put("translateY", TRANSLATE_Y_INDEX);
- put("scaleX", SCALE_X_INDEX);
- put("scaleY", SCALE_Y_INDEX);
- put("pivotX", PIVOT_X_INDEX);
- put("pivotY", PIVOT_Y_INDEX);
- put("rotation", ROTATION_INDEX);
- }
- };
+ private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+ "translateX", TRANSLATE_X_INDEX,
+ "translateY", TRANSLATE_Y_INDEX,
+ "scaleX", SCALE_X_INDEX,
+ "scaleY", SCALE_Y_INDEX,
+ "pivotX", PIVOT_X_INDEX,
+ "pivotY", PIVOT_Y_INDEX,
+ "rotation", ROTATION_INDEX);
static int getPropertyIndex(String propertyName) {
if (sPropertyIndexMap.containsKey(propertyName)) {
@@ -1285,18 +1281,15 @@
}
};
- private static final HashMap<String, Property> sPropertyMap =
- new HashMap<String, Property>() {
- {
- put("translateX", TRANSLATE_X);
- put("translateY", TRANSLATE_Y);
- put("scaleX", SCALE_X);
- put("scaleY", SCALE_Y);
- put("pivotX", PIVOT_X);
- put("pivotY", PIVOT_Y);
- put("rotation", ROTATION);
- }
- };
+ private static final Map<String, Property> sPropertyMap = Map.of(
+ "translateX", TRANSLATE_X,
+ "translateY", TRANSLATE_Y,
+ "scaleX", SCALE_X,
+ "scaleY", SCALE_Y,
+ "pivotX", PIVOT_X,
+ "pivotY", PIVOT_Y,
+ "rotation", ROTATION);
+
// Temp array to store transform values obtained from native.
private float[] mTransform;
/////////////////////////////////////////////////////
@@ -1762,19 +1755,15 @@
private static final int NATIVE_ALLOCATION_SIZE = 264;
// Property map for animatable attributes.
- private final static HashMap<String, Integer> sPropertyIndexMap
- = new HashMap<String, Integer> () {
- {
- put("strokeWidth", STROKE_WIDTH_INDEX);
- put("strokeColor", STROKE_COLOR_INDEX);
- put("strokeAlpha", STROKE_ALPHA_INDEX);
- put("fillColor", FILL_COLOR_INDEX);
- put("fillAlpha", FILL_ALPHA_INDEX);
- put("trimPathStart", TRIM_PATH_START_INDEX);
- put("trimPathEnd", TRIM_PATH_END_INDEX);
- put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
- }
- };
+ private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+ "strokeWidth", STROKE_WIDTH_INDEX,
+ "strokeColor", STROKE_COLOR_INDEX,
+ "strokeAlpha", STROKE_ALPHA_INDEX,
+ "fillColor", FILL_COLOR_INDEX,
+ "fillAlpha", FILL_ALPHA_INDEX,
+ "trimPathStart", TRIM_PATH_START_INDEX,
+ "trimPathEnd", TRIM_PATH_END_INDEX,
+ "trimPathOffset", TRIM_PATH_OFFSET_INDEX);
// Below are the Properties that wrap the setters to avoid reflection overhead in animations
private static final Property<VFullPath, Float> STROKE_WIDTH =
@@ -1881,19 +1870,15 @@
}
};
- private final static HashMap<String, Property> sPropertyMap
- = new HashMap<String, Property> () {
- {
- put("strokeWidth", STROKE_WIDTH);
- put("strokeColor", STROKE_COLOR);
- put("strokeAlpha", STROKE_ALPHA);
- put("fillColor", FILL_COLOR);
- put("fillAlpha", FILL_ALPHA);
- put("trimPathStart", TRIM_PATH_START);
- put("trimPathEnd", TRIM_PATH_END);
- put("trimPathOffset", TRIM_PATH_OFFSET);
- }
- };
+ private static final Map<String, Property> sPropertyMap = Map.of(
+ "strokeWidth", STROKE_WIDTH,
+ "strokeColor", STROKE_COLOR,
+ "strokeAlpha", STROKE_ALPHA,
+ "fillColor", FILL_COLOR,
+ "fillAlpha", FILL_ALPHA,
+ "trimPathStart", TRIM_PATH_START,
+ "trimPathEnd", TRIM_PATH_END,
+ "trimPathOffset", TRIM_PATH_OFFSET);
// Temp array to store property data obtained from native getter.
private byte[] mPropertyData;
diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java
index 09799fd..48969aa 100644
--- a/graphics/java/android/graphics/fonts/FontStyle.java
+++ b/graphics/java/android/graphics/fonts/FontStyle.java
@@ -48,6 +48,10 @@
private static final String TAG = "FontStyle";
/**
+ * A default value when font weight is unspecified
+ */
+ public static final int FONT_WEIGHT_UNSPECIFIED = -1;
+ /**
* A minimum weight value for the font
*/
public static final int FONT_WEIGHT_MIN = 1;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 74303e2..d7d43aa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -18,6 +18,12 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
+
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -31,7 +37,6 @@
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -79,26 +84,20 @@
@Override
public void unregisterOrganizer() {
if (mAnimationController != null) {
- mAnimationController.unregisterAllRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
mAnimationController = null;
}
super.unregisterOrganizer();
}
- /** Overrides the animation if the transition is on the given Task. */
- void startOverrideSplitAnimation(int taskId) {
+ /**
+ * Overrides the animation for transitions of embedded activities organized by this organizer.
+ */
+ void overrideSplitAnimation() {
if (mAnimationController == null) {
mAnimationController = new TaskFragmentAnimationController(this);
}
- mAnimationController.registerRemoteAnimations(taskId);
- }
-
- /** No longer overrides the animation if the transition is on the given Task. */
- @GuardedBy("mLock")
- void stopOverrideSplitAnimation(int taskId) {
- if (mAnimationController != null) {
- mAnimationController.unregisterRemoteAnimations(taskId);
- }
+ mAnimationController.registerRemoteAnimations();
}
/**
@@ -140,6 +139,8 @@
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
+ false /* isStacked */);
}
/**
@@ -215,6 +216,28 @@
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
+ boolean isStacked) {
+ final boolean finishPrimaryWithSecondary;
+ if (isStacked) {
+ finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishPrimaryWithSecondaryBehavior(splitRule));
+ } else {
+ finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
+ }
+ wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
+
+ final boolean finishSecondaryWithPrimary;
+ if (isStacked) {
+ finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishSecondaryWithPrimaryBehavior(splitRule));
+ } else {
+ finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
+ }
+ wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
+ }
+
TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
if (mFragmentInfos.containsKey(fragmentToken)) {
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 16760e26..c06548a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -193,7 +193,6 @@
continue;
}
updateContainersInTask(wct, taskContainer);
- updateAnimationOverride(taskContainer);
}
// The WCT should be applied and merged to the device state change transition if
// there is one.
@@ -208,9 +207,6 @@
synchronized (mLock) {
mSplitRules.clear();
mSplitRules.addAll(rules);
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- updateAnimationOverride(mTaskContainers.valueAt(i));
- }
}
}
@@ -389,6 +385,10 @@
// launching activity in the Task.
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) {
+ // Do not finish the dependents if this TaskFragment was cleared to reorder
+ // the launching Activity to front of the Task.
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
@@ -608,7 +608,6 @@
}
if (taskContainer.isEmpty()) {
// Cleanup the TaskContainer if it becomes empty.
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
mTaskContainers.remove(taskContainer.getTaskId());
}
return;
@@ -618,43 +617,7 @@
@GuardedBy("mLock")
private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
@NonNull Configuration config) {
- final boolean wasInPip = taskContainer.isInPictureInPicture();
- final boolean isInPIp = isInPictureInPicture(config);
-
- // We need to check the animation override when enter/exit PIP or has bounds changed.
- boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
- if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
- && !isInPIp) {
- // We don't care the bounds change when it has already entered PIP.
- shouldUpdateAnimationOverride = true;
- }
- if (shouldUpdateAnimationOverride) {
- updateAnimationOverride(taskContainer);
- }
- }
-
- /**
- * Updates if we should override transition animation. We only want to override if the Task
- * bounds is large enough for at least one split rule.
- */
- @GuardedBy("mLock")
- private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
- if (ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/207070762): cleanup with legacy app transition
- // Animation will be handled by WM Shell with Shell transition enabled.
- return;
- }
- if (!taskContainer.isTaskBoundsInitialized()) {
- // We don't know about the Task bounds/windowingMode yet.
- return;
- }
-
- // We only want to override if the TaskContainer may show split.
- if (mayShowSplit(taskContainer)) {
- mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
- } else {
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
- }
+ taskContainer.setTaskBounds(config.windowConfiguration.getBounds());
}
/** Returns whether the given {@link TaskContainer} may show in split. */
@@ -1279,7 +1242,6 @@
Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
}
}
- updateAnimationOverride(taskContainer);
return container;
}
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 362f1fa..f494b32 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -139,6 +139,11 @@
super(executor, controller);
mController = controller;
registerOrganizer();
+ if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/207070762): cleanup with legacy app transition
+ // Animation will be handled by WM Shell when Shell transition is enabled.
+ overrideSplitAnimation();
+ }
}
/**
@@ -371,13 +376,16 @@
@NonNull SplitAttributes splitAttributes) {
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the
// secondaryContainer could not be finished.
- if (!shouldShowSplit(splitAttributes)) {
+ boolean isStacked = !shouldShowSplit(splitAttributes);
+ if (isStacked) {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
null /* secondary */, null /* splitRule */);
} else {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule);
}
+ setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
}
/**
@@ -489,8 +497,15 @@
|| splitContainer.getSecondaryContainer().getInfo() == null) {
return RESULT_EXPAND_FAILED_NO_TF_INFO;
}
- expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken());
- expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken());
+ final IBinder primaryToken =
+ splitContainer.getPrimaryContainer().getTaskFragmentToken();
+ final IBinder secondaryToken =
+ splitContainer.getSecondaryContainer().getTaskFragmentToken();
+ expandTaskFragment(wct, primaryToken);
+ expandTaskFragment(wct, secondaryToken);
+ // Set the companion TaskFragment when the two containers stacked.
+ setCompanionTaskFragment(wct, primaryToken, secondaryToken,
+ splitContainer.getSplitRule(), true /* isStacked */);
return RESULT_EXPANDED;
}
return RESULT_NOT_EXPANDED;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index ee2e139..d7eb9a0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -18,13 +18,10 @@
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import android.util.ArraySet;
import android.util.Log;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -44,8 +41,7 @@
private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
@VisibleForTesting
final RemoteAnimationDefinition mDefinition;
- /** Task Ids that we have registered for remote animation. */
- private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
+ private boolean mIsRegistered;
TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
@@ -54,39 +50,30 @@
new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
}
- void registerRemoteAnimations(int taskId) {
+ void registerRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "registerRemoteAnimations");
}
- if (mRegisterTasks.contains(taskId)) {
+ if (mIsRegistered) {
return;
}
- mOrganizer.registerRemoteAnimations(taskId, mDefinition);
- mRegisterTasks.add(taskId);
+ mOrganizer.registerRemoteAnimations(mDefinition);
+ mIsRegistered = true;
}
- void unregisterRemoteAnimations(int taskId) {
+ void unregisterRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "unregisterRemoteAnimations");
}
- if (!mRegisterTasks.contains(taskId)) {
+ if (!mIsRegistered) {
return;
}
- mOrganizer.unregisterRemoteAnimations(taskId);
- mRegisterTasks.remove(taskId);
- }
-
- void unregisterAllRemoteAnimations() {
- final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
- for (int taskId : tasks) {
- unregisterRemoteAnimations(taskId);
- }
+ mOrganizer.unregisterRemoteAnimations();
+ mIsRegistered = false;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8c416e8..0e13c59 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -20,11 +20,9 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import android.animation.Animator;
@@ -169,11 +167,9 @@
switch (transit) {
case TRANSIT_OLD_ACTIVITY_OPEN:
case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- case TRANSIT_OLD_TASK_OPEN:
return createOpenAnimationAdapters(targets);
case TRANSIT_OLD_ACTIVITY_CLOSE:
case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- case TRANSIT_OLD_TASK_CLOSE:
return createCloseAnimationAdapters(targets);
case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
return createChangeAnimationAdapters(targets);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index b516e140..2192b5c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -36,6 +36,7 @@
import android.os.IBinder;
import android.util.ArrayMap;
import android.window.WindowContext;
+import android.window.WindowProvider;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -71,7 +72,7 @@
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
- private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+ private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
new ArrayMap<>();
public WindowLayoutComponentImpl(@NonNull Context context) {
@@ -121,21 +122,21 @@
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
- + " an Activity or a WindowContext");
+ + " an Activity, WindowContext or InputMethodService");
}
mFoldingFeatureProducer.getData((features) -> {
- // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
});
mWindowLayoutChangeListeners.put(context, consumer);
- if (context instanceof WindowContext) {
+ // TODO(b/258065175) Further extend this to ContextWrappers.
+ if (context instanceof WindowProvider) {
final IBinder windowContextToken = context.getWindowContextToken();
- final WindowContextConfigListener listener =
- new WindowContextConfigListener(windowContextToken);
+ final ConfigurationChangeListener listener =
+ new ConfigurationChangeListener(windowContextToken);
context.registerComponentCallbacks(listener);
- mWindowContextConfigListeners.put(windowContextToken, listener);
+ mConfigurationChangeListeners.put(windowContextToken, listener);
}
}
@@ -150,10 +151,10 @@
if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
continue;
}
- if (context instanceof WindowContext) {
+ if (context instanceof WindowProvider) {
final IBinder token = context.getWindowContextToken();
- context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
- mWindowContextConfigListeners.remove(token);
+ context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token));
+ mConfigurationChangeListeners.remove(token);
}
break;
}
@@ -349,10 +350,10 @@
}
}
- private final class WindowContextConfigListener implements ComponentCallbacks {
+ private final class ConfigurationChangeListener implements ComponentCallbacks {
final IBinder mToken;
- WindowContextConfigListener(IBinder token) {
+ ConfigurationChangeListener(IBinder token) {
mToken = token;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 40f7a27..92011af 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -169,6 +169,7 @@
new Point(),
false /* isTaskClearedForReuse */,
false /* isTaskFragmentClearedForPip */,
+ false /* isClearedForReorderActivityToFront */,
new Point());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 957a248..31aa09c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -18,7 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -26,10 +25,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.content.Intent;
import android.content.res.Configuration;
@@ -85,35 +82,20 @@
@Test
public void testUnregisterOrganizer() {
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
+ mOrganizer.overrideSplitAnimation();
mOrganizer.unregisterOrganizer();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
@Test
- public void testStartOverrideSplitAnimation() {
+ public void testOverrideSplitAnimation() {
assertNull(mOrganizer.mAnimationController);
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
+ mOrganizer.overrideSplitAnimation();
assertNotNull(mOrganizer.mAnimationController);
- verify(mOrganizer).registerRemoteAnimations(TASK_ID,
- mOrganizer.mAnimationController.mDefinition);
- }
-
- @Test
- public void testStopOverrideSplitAnimation() {
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
-
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
}
@Test
@@ -144,6 +126,6 @@
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
false /* isVisible */, new ArrayList<>(), new Point(),
false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
- new Point());
+ false /* isClearedForReorderActivityToFront */, new Point());
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
index d31342b..379ea0c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -16,11 +16,8 @@
package androidx.window.extensions.embedding;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import android.platform.test.annotations.Presubmit;
@@ -57,41 +54,31 @@
@Test
public void testRegisterRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
// No extra call if it has been registered.
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
}
@Test
public void testUnregisterRemoteAnimations() {
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No call if it is not registered.
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+ verify(mOrganizer, never()).unregisterRemoteAnimations();
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).unregisterRemoteAnimations();
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No extra call if it has been unregistered.
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- }
-
- @Test
- public void testUnregisterAllRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.registerRemoteAnimations(TASK_ID + 1);
- mAnimationController.unregisterAllRemoteAnimations();
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
}
diff --git a/core/res/res/values-television/styles.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
similarity index 70%
rename from core/res/res/values-television/styles.xml
rename to libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
index ad9140c..416287d 100644
--- a/core/res/res/values-television/styles.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -13,9 +14,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<resources>
- <style name="PermissionGrantButtonTop"
- parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" />
- <style name="PermissionGrantButtonBottom"
- parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" />
-</resources>
\ No newline at end of file
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="20dp" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
index d9a140b..582a11c 100644
--- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
-android:background="@drawable/decor_caption_title">
+android:background="@drawable/decor_caption_menu_background">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/fullscreen_button"
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index fc0c20e..42da075 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
<string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
<string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Volskerm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Rekenaarmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Verdeelde skerm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Sweef"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 405cb06..ef53c95 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
<string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"የዴስክቶፕ ሁነታ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገጽ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 9321d8d..f5ea409 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
<string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
<string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ملء الشاشة"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"وضع سطح المكتب"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"تقسيم الشاشة"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"المزيد"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"نافذة عائمة"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 268827c..c4cfcf7 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
<string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
<string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ ম’ড"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"বিভাজিত স্ক্ৰীন"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"অধিক"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ওপঙা"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 779bfb6..84f706a 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
<string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
<string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Rejimi"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ardı"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Üzən pəncərə"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 1e78b3c..2eb1ac2 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Preko celog ekrana"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim za računare"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podeljeni ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Još"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plutajuće"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 5524c19..b6ce785 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"На ўвесь экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Рэжым працоўнага стала"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Падзяліць экран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Яшчэ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Зрабіць рухомым акном"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 42dbe3e..ce22914 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Цял екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за настолни компютри"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Разделяне на екрана"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Още"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плаващо"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 31a11cd..52ae816 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
<string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
<string name="handle_text" msgid="1766582106752184456">"হাতল"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ফুলস্ক্রিন"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ মোড"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"স্প্লিট স্ক্রিন"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"আরও"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ফ্লোট"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 1d674a5..e7ff6b6 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Cijeli ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Način rada radne površine"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podijeljeni ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebdeći"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 3346938..43c8bad 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
<string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
<string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode d\'escriptori"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Més"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotant"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 555c252..0d68e46 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
<string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
<string name="handle_text" msgid="1766582106752184456">"Úchyt"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim počítače"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Rozdělená obrazovka"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Více"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plovoucí"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 829bb91..28c0064 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
<string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fuld skærm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Computertilstand"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Opdelt skærm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mere"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svævende"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index b3bd9ea..41af26d 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
<string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
<string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 79e2dab..3e08ee1 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
<string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
<string name="handle_text" msgid="1766582106752184456">"Λαβή"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Πλήρης οθόνη"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Λειτουργία επιφάνειας εργασίας"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Διαχωρισμός οθόνης"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Περισσότερα"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Κινούμενο"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 6db010a..231c2649 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 6db010a..431c7ec 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -22,7 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
- <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-Picture Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string>
<string name="pip_play" msgid="3496151081459417097">"Play"</string>
@@ -36,8 +36,8 @@
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
- <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+ <string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Split-screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -66,9 +66,9 @@
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles at any time"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles anytime"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tap Manage to turn off bubbles from this app"</string>
- <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
+ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
@@ -83,9 +83,14 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
- <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
- <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
index 71d02271..09def6b 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -17,15 +17,15 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="2955969519031223530">"Close"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
<string name="pip_move" msgid="158770205886688553">"Move"</string>
<string name="pip_expand" msgid="1051966011679297308">"Expand"</string>
<string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string>
- <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
- <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Double press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-Picture menu."</string>
<string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string>
<string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string>
<string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 6db010a..231c2649 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 6db010a..231c2649 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 37b4fc7..f3e60d2 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 1e7174d..fe29baa 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 87cdca4..8f20e16 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo Escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index f392560..698e5cc 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
<string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Täisekraan"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Lauaarvuti režiim"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Jagatud ekraanikuva"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Rohkem"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Hõljuv"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 6bc1d91..629c745 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
<string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 9949dd2..b7920ef 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
<string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
<string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"تمامصفحه"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"حالت رایانه"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"صفحهٔ دونیمه"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"بیشتر"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"شناور"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index e701452..18def67 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
<string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
<string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Koko näyttö"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Työpöytätila"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Jaettu näyttö"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lisää"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Kelluva ikkuna"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index ff8417b..5146d1c 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 8ceeec0..1ee8f68 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode ordinateur"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flottante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 999921a..6a8add8 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Máis"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 5207e19..de131995 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
<string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string>
<string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"પૂર્ણસ્ક્રીન"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ડેસ્કટૉપ મોડ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"સ્ક્રીનને વિભાજિત કરો"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"વધુ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ફ્લોટિંગ વિન્ડો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 1b9b90b..df0ebb3 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
<string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
<string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"फ़ुलस्क्रीन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन मोड"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ज़्यादा देखें"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ़्लोट"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 08aa262..8d20c9d1 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
<string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
<string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Puni zaslon"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Stolni način rada"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Razdvojeni zaslon"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plutajući"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 8ad0a01..ed4c354 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
<string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
<string name="handle_text" msgid="1766582106752184456">"Fogópont"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Teljes képernyő"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Asztali üzemmód"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Osztott képernyő"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Továbbiak"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebegő"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index b61ea1d..31ead01 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
<string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
<string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Լիաէկրան"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Համակարգչի ռեժիմ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Տրոհված էկրան"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ավելին"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Լողացող պատուհան"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 79e926d..e0e1801 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
<string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
<string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Layar Penuh"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode Desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Layar Terpisah"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lainnya"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Mengambang"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 0645c41..4b0e812 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
<string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
<string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Allur skjárinn"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Skjáborðsstilling"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Skjáskipting"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meira"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Reikult"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 9a023f5..4226a84 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
<string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Schermo intero"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modalità desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Schermo diviso"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Altro"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Mobile"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 57238ea..038a2232 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
<string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
<string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"מסך מלא"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ממשק המחשב"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"מסך מפוצל"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"עוד"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"בלונים"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index d0b5462..315f074 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
<string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
<string name="handle_text" msgid="1766582106752184456">"ハンドル"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全画面表示"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"デスクトップ モード"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割画面"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"その他"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"フローティング"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index b0cf539..1ff6ff8 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
<string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
<string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"სრულ ეკრანზე"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"დესკტოპის რეჟიმი"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ეკრანის გაყოფა"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"სხვა"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ფარფატი"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index a9f350e..933d0ca 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
<string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
<string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Толық экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Компьютер режимі"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлу"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Қосымша"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Қалқыма"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index a1d2691..c1cb1dd 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
<string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"អេក្រង់ពេញ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"មុខងារកុំព្យូទ័រ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"មុខងារបំបែកអេក្រង់"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ច្រើនទៀត"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"អណ្ដែត"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index acad7c1..c7fe59b 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
<string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ಫುಲ್ಸ್ಕ್ರೀನ್"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ಡೆಸ್ಕ್ಟಾಪ್ ಮೋಡ್"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ಇನ್ನಷ್ಟು"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ಫ್ಲೋಟ್"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index f6ea6cc..17c51d9 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
<string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
<string name="handle_text" msgid="1766582106752184456">"핸들"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"전체 화면"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"데스크톱 모드"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"화면 분할"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"더보기"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"플로팅"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 9ad82de..4329276 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
<string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Толук экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Компьютер режими"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлүү"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Дагы"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Калкыма"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 53d4f34..e0a92b8 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
<string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ເຕັມຈໍ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ໂໝດເດັສທັອບ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ແບ່ງໜ້າຈໍ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ເພີ່ມເຕີມ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ລອຍ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 331281a..50565a7 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
<string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Visas ekranas"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Stalinio kompiuterio režimas"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Išskaidyto ekrano režimas"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Daugiau"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Slankusis langas"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index d301721..7126094 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
<string name="handle_text" msgid="1766582106752184456">"Turis"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pilnekrāna režīms"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Darbvirsmas režīms"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Sadalīt ekrānu"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Vairāk"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Peldošs"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 00f2900..41f549e 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Прекар"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Цел екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за компјутер"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Поделен екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Повеќе"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Лебдечко"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 9722868..b21e5b4 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
<string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
<string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"പൂർണ്ണസ്ക്രീൻ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ഡെസ്ക്ടോപ്പ് മോഡ്"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"സ്ക്രീൻ വിഭജനം"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"കൂടുതൽ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ഫ്ലോട്ട്"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 3d598e4..d713258 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
<string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
<string name="handle_text" msgid="1766582106752184456">"Бариул"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Бүтэн дэлгэц"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Дэлгэцийн горим"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Дэлгэцийг хуваах"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Бусад"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Хөвөгч"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 29f57fb..1b53175 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
<string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
<string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"फुलस्क्रीन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"आणखी"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 4dc8dca..e648a7a 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
<string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
<string name="handle_text" msgid="1766582106752184456">"Pemegang"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Skrin penuh"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mod Desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Skrin Pisah"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lagi"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Terapung"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 0bb6acf..96a6412 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
<string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
<string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ဖန်သားပြင်အပြည့်"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ဒက်စ်တော့မုဒ်"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"မျက်နှာပြင် ခွဲ၍ပြသရန်"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ပိုပြပါ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"မျှောရန်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index f4f3af8..b6cea3f 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -66,7 +66,7 @@
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat med bobler"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne en boble. Dra for å flytte den."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontrollér bobler når som helst"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontroller bobler når som helst"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Trykk på Administrer for å slå av bobler for denne appen"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string>
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
<string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullskjerm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Skrivebordmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Delt skjerm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 4b90e92..4413a43 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
<string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
<string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"फुल स्क्रिन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रिन"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"थप"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 18a2021..2e45143 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
<string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
<string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Volledig scherm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Gesplitst scherm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Zwevend"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 9e5a96d..7f0897f 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
<string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ପୂର୍ଣ୍ଣସ୍କ୍ରିନ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ଡେସ୍କଟପ ମୋଡ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ଅଧିକ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ଫ୍ଲୋଟ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 5c255d8..aa250d5 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
<string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ਪੂਰੀ-ਸਕ੍ਰੀਨ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ਡੈਸਕਟਾਪ ਮੋਡ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ਹੋਰ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ਫ਼ਲੋਟ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 086726c..3c3d7aa 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
<string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
<string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pełny ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Tryb pulpitu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podzielony ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Więcej"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pływające"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 353c02d..cad59e0 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
<string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 97d40b5..26772dc 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Anterior"</string>
<string name="handle_text" msgid="1766582106752184456">"Indicador"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ecrã inteiro"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo de ambiente de trabalho"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ecrã dividido"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flutuar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 353c02d..cad59e0 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
<string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index a085f02..607d997 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
<string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
<string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ecran complet"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modul desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ecran împărțit"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mai multe"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotantă"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 97c5a06..bed76e4 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Полноэкранный режим"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим компьютера"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Разделить экран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ещё"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плавающее окно"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 60e1222..0ed3b8e 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
<string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
<string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"පූර්ණ තිරය"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ඩෙස්ක්ටොප් ප්රකාරය"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"බෙදුම් තිරය"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"තව"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"පාවෙන"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 4007498..f20e940 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
<string name="back_button_text" msgid="1469718707134137085">"Späť"</string>
<string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim počítača"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Rozdelená obrazovka"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Viac"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plávajúce"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 6dbd883..e2063d8 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
<string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celozaslonsko"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Namizni način"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Razdeljen zaslon"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Več"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebdeče"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 11e5713..0c3947d 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
<string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
<string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modaliteti i desktopit"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 5beb31c..140d3db 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Преко целог екрана"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за рачунаре"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Подељени екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Још"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плутајуће"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index c175583..62220c4 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
<string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Helskärm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Datorläge"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Delad skärm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svävande"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index a049364..232b268 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
<string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
<string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Skrini nzima"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Hali ya Kompyuta ya mezani"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Gawa Skrini"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Zaidi"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Inayoelea"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 21b7412..90bf263 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
<string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
<string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"முழுத்திரை"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"டெஸ்க்டாப் பயன்முறை"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"திரையைப் பிரிக்கும்"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"கூடுதல் விருப்பத்தேர்வுகள்"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"மிதக்கும் சாளரம்"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 18c3719..7a2a8a8 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
<string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
<string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ఫుల్-స్క్రీన్"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"డెస్క్టాప్ మోడ్"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"స్ప్లిట్ స్క్రీన్"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"మరిన్ని"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ఫ్లోట్"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 9e11d66..76cbb1a 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
<string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
<string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"เต็มหน้าจอ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"โหมดเดสก์ท็อป"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"แยกหน้าจอ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"เพิ่มเติม"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ล่องลอย"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index fbe0347..103d072 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
<string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Higit pa"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index dca58b8..0b82db7 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
<string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
<string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Modu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Daha Fazla"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Havada Süzülen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index f7a59d3..59b7673 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"На весь екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим комп’ютера"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Розділити екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Більше"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плаваюче вікно"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 0ff1b6c..743c063 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
<string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
<string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"مکمل اسکرین"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ڈیسک ٹاپ موڈ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"اسپلٹ اسکرین"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"مزید"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"فلوٹ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index ee9b4dc..3b9324f 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
<string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Butun ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop rejimi"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ekranni ikkiga ajratish"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Yana"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pufakli"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 67ab2a0..8583621 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
<string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
<string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Toàn màn hình"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Chế độ máy tính"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Chia đôi màn hình"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Tuỳ chọn khác"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Nổi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 8fdf1d4..d20626d 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
<string name="back_button_text" msgid="1469718707134137085">"返回"</string>
<string name="handle_text" msgid="1766582106752184456">"处理"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全屏"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分屏"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"悬浮"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 5dce250..f53dc7d 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
<string name="back_button_text" msgid="1469718707134137085">"返去"</string>
<string name="handle_text" msgid="1766582106752184456">"控點"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割螢幕"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index c449c2e..4b33ab6 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
<string name="back_button_text" msgid="1469718707134137085">"返回"</string>
<string name="handle_text" msgid="1766582106752184456">"控點"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"電腦模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割畫面"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index b470e7f..cd128b6 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -88,4 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
<string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
<string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Isikrini esigcwele"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Imodi Yedeskithophu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Hlukanisa isikrini"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Okwengeziwe"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Iflowuthi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index df5f921..c6197c8 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -111,4 +111,8 @@
<!-- Whether to dim a split-screen task when the other is the IME target -->
<bool name="config_dimNonImeAttachedSide">true</bool>
+
+ <!-- Components support to launch multiple instances into split-screen -->
+ <string-array name="config_componentsSupportMultiInstancesSplit">
+ </string-array>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index dec1e38..065fd95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,14 +16,12 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -49,7 +47,6 @@
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
-import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -568,6 +565,22 @@
}
}
+ /**
+ * Return list of {@link RunningTaskInfo}s for the given display.
+ *
+ * @return filtered list of tasks or empty list
+ */
+ public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
+ ArrayList<RunningTaskInfo> result = new ArrayList<>();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ result.add(taskInfo);
+ }
+ }
+ return result;
+ }
+
/** Gets running task by taskId. Returns {@code null} if no such task observed. */
@Nullable
public RunningTaskInfo getRunningTaskInfo(int taskId) {
@@ -694,57 +707,6 @@
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
- /**
- * Create a {@link WindowContainerTransaction} to clear task bounds.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have bounds cleared
- * @return {@link WindowContainerTransaction} with pending operations to clear bounds
- */
- public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
- == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
- taskInfo.token, taskInfo);
- wct.setBounds(taskInfo.token, null);
- }
- }
- return wct;
- }
-
- /**
- * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have windowing mode reset to {@link
- * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
- * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
- */
- public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if (taskInfo.displayId == displayId
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
- taskInfo);
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- return wct;
- }
-
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
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 64220c8..f811940 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
@@ -38,7 +38,6 @@
import android.provider.Settings.Global;
import android.util.Log;
import android.util.SparseArray;
-import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowFocusObserver;
import android.view.InputDevice;
@@ -62,7 +61,6 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -70,7 +68,7 @@
* Controls the window animation run when a user initiates a back gesture.
*/
public class BackAnimationController implements RemoteCallable<BackAnimationController> {
- private static final String TAG = "BackAnimationController";
+ private static final String TAG = "ShellBackPreview";
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
public static final boolean IS_ENABLED =
@@ -82,19 +80,16 @@
SETTING_VALUE_OFF) == SETTING_VALUE_ON;
/** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
- // TODO (b/241808055) Find a appropriate time to remove during refactor
- private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS;
/**
- * Max duration to wait for a transition to finish before accepting another gesture start
- * request.
+ * Max duration to wait for an animation to finish before triggering the real back.
*/
- private static final long MAX_TRANSITION_DURATION = 2000;
+ private static final long MAX_ANIMATION_DURATION = 2000;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
- /** Tracks if an uninterruptible transition is in progress */
- private boolean mTransitionInProgress = false;
+ /** Tracks if an uninterruptible animation is in progress */
+ private boolean mPostCommitAnimationInProgress = false;
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
/** @see #setTriggerBack(boolean) */
@@ -108,9 +103,9 @@
private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
- private final Runnable mResetTransitionRunnable = () -> {
- ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...",
- MAX_TRANSITION_DURATION);
+ private final Runnable mAnimationTimeoutRunnable = () -> {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
+ MAX_ANIMATION_DURATION);
onBackAnimationFinished();
};
@@ -121,8 +116,8 @@
private final TouchTracker mTouchTracker = new TouchTracker();
private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
- private final Transitions mTransitions;
- private BackTransitionHandler mBackTransitionHandler;
+ @Nullable
+ private IOnBackInvokedCallback mActiveCallback;
@VisibleForTesting
final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@@ -131,9 +126,9 @@
@Override
public void focusLost(IBinder inputToken) {
mShellExecutor.execute(() -> {
- if (!mBackGestureStarted || mTransitionInProgress) {
- // If an uninterruptible transition is already in progress, we should ignore
- // this due to the transition may cause focus lost. (alpha = 0)
+ if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+ // If an uninterruptible animation is already in progress, we should ignore
+ // this due to it may cause focus lost. (alpha = 0)
return;
}
ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
@@ -148,11 +143,9 @@
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
- Context context,
- Transitions transitions) {
+ Context context) {
this(shellInit, shellController, shellExecutor, backgroundHandler,
- ActivityTaskManager.getService(), context, context.getContentResolver(),
- transitions);
+ ActivityTaskManager.getService(), context, context.getContentResolver());
}
@VisibleForTesting
@@ -162,8 +155,7 @@
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver,
- Transitions transitions) {
+ Context context, ContentResolver contentResolver) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -171,7 +163,6 @@
mContentResolver = contentResolver;
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
- mTransitions = transitions;
}
@VisibleForTesting
@@ -182,10 +173,6 @@
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
createAdapter();
- if (ENABLE_SHELL_TRANSITIONS) {
- mBackTransitionHandler = new BackTransitionHandler(this);
- mTransitions.addHandler(mBackTransitionHandler);
- }
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
@@ -193,26 +180,15 @@
}
private void initBackAnimationRunners() {
- final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
- final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- // Animation missing. Simply finish animation.
- finishedCallback.onAnimationFinished();
- }
- };
+ if (!IS_U_ANIMATION_ENABLED) {
+ return;
+ }
- final BackAnimationRunner dummyBackRunner =
- new BackAnimationRunner(dummyCallback, dummyRunner);
final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
// TODO (238474994): register cross activity animation when it's completed.
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
// TODO (236760237): register dialog close animation when it's completed.
- mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -237,8 +213,7 @@
Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
boolean isEnabled = settingValue == SETTING_VALUE_ON;
mEnableAnimations.set(isEnabled);
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s",
- isEnabled);
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
public BackAnimation getBackAnimationImpl() {
@@ -292,7 +267,9 @@
public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
IRemoteAnimationRunner runner) {
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
- (controller) -> controller.setBackToLauncherCallback(callback, runner));
+ (controller) -> controller.registerAnimation(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ new BackAnimationRunner(callback, runner)));
}
@Override
@@ -307,54 +284,22 @@
}
}
- @VisibleForTesting
- void setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
- mAnimationDefinition.set(BackNavigationInfo.TYPE_RETURN_TO_HOME,
- new BackAnimationRunner(callback, runner));
+ void registerAnimation(@BackNavigationInfo.BackTargetType int type,
+ @NonNull BackAnimationRunner runner) {
+ mAnimationDefinition.set(type, runner);
}
private void clearBackToLauncherCallback() {
mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
}
- @VisibleForTesting
- void onBackAnimationFinished() {
- if (!mTransitionInProgress) {
- return;
- }
-
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
-
- // Trigger real back.
- if (mBackNavigationInfo != null) {
- IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
- if (mTriggerBack) {
- dispatchOnBackInvoked(callback);
- } else {
- dispatchOnBackCancelled(callback);
- }
- }
-
- // In legacy transition, it would use `Task.mBackGestureStarted` in core to handle the
- // following transition when back callback is invoked.
- // If the back callback is not invoked, we should reset the token and finish the whole back
- // navigation without waiting the transition.
- if (!ENABLE_SHELL_TRANSITIONS) {
- finishBackNavigation();
- } else if (!mTriggerBack) {
- // reset the token to prevent it consume next transition.
- mBackTransitionHandler.setDepartingWindowContainerToken(null);
- finishBackNavigation();
- }
- }
-
/**
* Called when a new motion event needs to be transferred to this
* {@link BackAnimationController}
*/
public void onMotionEvent(float touchX, float touchY, int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
- if (mTransitionInProgress) {
+ if (mPostCommitAnimationInProgress) {
return;
}
@@ -371,7 +316,7 @@
onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false;
}
- onMove(touchX, touchY, swipeEdge);
+ onMove();
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
@@ -409,30 +354,28 @@
return;
}
final int backType = backNavigationInfo.getType();
- final IOnBackInvokedCallback targetCallback;
- final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
+ final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (shouldDispatchToAnimator) {
- mAnimationDefinition.get(backType).startGesture();
+ if (mAnimationDefinition.contains(backType)) {
+ mActiveCallback = mAnimationDefinition.get(backType).getCallback();
+ mAnimationDefinition.get(backType).startGesture();
+ } else {
+ mActiveCallback = null;
+ }
} else {
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- dispatchOnBackStarted(targetCallback, mTouchTracker.createStartEvent(null));
+ mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+ dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
}
}
- private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
- if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
+ private void onMove() {
+ if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()
+ || mActiveCallback == null) {
return;
}
- final BackEvent backEvent = mTouchTracker.createProgressEvent();
- int backType = mBackNavigationInfo.getType();
- IOnBackInvokedCallback targetCallback;
- if (shouldDispatchToAnimator(backType)) {
- targetCallback = mAnimationDefinition.get(backType).getCallback();
- } else {
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- }
- dispatchOnBackProgressed(targetCallback, backEvent);
+ final BackEvent backEvent = mTouchTracker.createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backEvent);
}
private void injectBackKey() {
@@ -454,62 +397,10 @@
}
}
- private void onGestureFinished(boolean fromTouch) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
- if (!mBackGestureStarted) {
- finishBackNavigation();
- return;
- }
-
- if (fromTouch) {
- // Let touch reset the flag otherwise it will start a new back navigation and refresh
- // the info when received a new move event.
- mBackGestureStarted = false;
- }
-
- if (mTransitionInProgress) {
- return;
- }
-
- if (mBackNavigationInfo == null) {
- // No focus window found or core are running recents animation, inject back key as
- // legacy behavior.
- if (mTriggerBack) {
- injectBackKey();
- }
- finishBackNavigation();
- return;
- }
-
- int backType = mBackNavigationInfo.getType();
- boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
- final BackAnimationRunner runner = mAnimationDefinition.get(backType);
- IOnBackInvokedCallback targetCallback = shouldDispatchToAnimator
- ? runner.getCallback() : mBackNavigationInfo.getOnBackInvokedCallback();
-
- if (shouldDispatchToAnimator) {
- if (runner.onGestureFinished(mTriggerBack)) {
- Log.w(TAG, "Gesture released, but animation didn't ready.");
- return;
- }
- startTransition();
- }
- if (mTriggerBack) {
- dispatchOnBackInvoked(targetCallback);
- } else {
- dispatchOnBackCancelled(targetCallback);
- }
- if (!shouldDispatchToAnimator) {
- // Animation callback missing. Simply finish animation.
- finishBackNavigation();
- }
- }
-
- private boolean shouldDispatchToAnimator(int backType) {
+ private boolean shouldDispatchToAnimator() {
return mEnableAnimations.get()
&& mBackNavigationInfo != null
- && mBackNavigationInfo.isPrepareRemoteAnimation()
- && mAnimationDefinition.contains(backType);
+ && mBackNavigationInfo.isPrepareRemoteAnimation();
}
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
@@ -518,7 +409,7 @@
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackStarted(backEvent);
}
} catch (RemoteException e) {
@@ -542,7 +433,7 @@
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackCancelled();
}
} catch (RemoteException e) {
@@ -556,7 +447,7 @@
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackProgressed(backEvent);
}
} catch (RemoteException e) {
@@ -564,17 +455,11 @@
}
}
- private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
- return (IS_U_ANIMATION_ENABLED || callback == mAnimationDefinition.get(
- BackNavigationInfo.TYPE_RETURN_TO_HOME).getCallback())
- && mEnableAnimations.get();
- }
-
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
public void setTriggerBack(boolean triggerBack) {
- if (mTransitionInProgress) {
+ if (mPostCommitAnimationInProgress) {
return;
}
mTriggerBack = triggerBack;
@@ -585,58 +470,129 @@
mTouchTracker.setProgressThreshold(progressThreshold);
}
- @VisibleForTesting
- void finishBackNavigation() {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
- BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
- boolean triggerBack = mTriggerBack;
- mBackNavigationInfo = null;
- mTriggerBack = false;
- mShouldStartOnNextMoveEvent = false;
- mTouchTracker.reset();
- if (backNavigationInfo == null) {
- return;
- }
- stopTransition();
+ private void invokeOrCancelBack() {
+ // Make a synchronized call to core before dispatch back event to client side.
+ // If the close transition happens before the core receives onAnimationFinished, there will
+ // play a second close animation for that transition.
if (mBackAnimationFinishedCallback != null) {
try {
- mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
+ mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack);
} catch (RemoteException e) {
Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
}
mBackAnimationFinishedCallback = null;
}
- backNavigationInfo.onBackNavigationFinished(triggerBack);
- }
- private void startTransition() {
- if (mTransitionInProgress) {
- return;
+ if (mBackNavigationInfo != null) {
+ final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(callback);
+ } else {
+ dispatchOnBackCancelled(callback);
+ }
}
- mTransitionInProgress = true;
- if (ENABLE_SHELL_TRANSITIONS) {
- mBackTransitionHandler.setDepartingWindowContainerToken(
- mBackNavigationInfo.getDepartingWindowContainerToken());
- }
- mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
- }
-
- void stopTransition() {
- mShellExecutor.removeCallbacks(mResetTransitionRunnable);
- mTransitionInProgress = false;
+ finishBackNavigation();
}
/**
- * This should be called from {@link BackTransitionHandler#startAnimation} when the following
- * transition is triggered by the real back callback in {@link #onBackAnimationFinished}.
- * Will consume the default transition and finish current back navigation.
+ * Called when the gesture is released, then it could start the post commit animation.
*/
- void finishTransition(Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishTransition()");
- mShellExecutor.execute(() -> {
+ private void onGestureFinished(boolean fromTouch) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+ if (!mBackGestureStarted) {
finishBackNavigation();
- finishCallback.onTransitionFinished(null, null);
- });
+ return;
+ }
+
+ if (fromTouch) {
+ // Let touch reset the flag otherwise it will start a new back navigation and refresh
+ // the info when received a new move event.
+ mBackGestureStarted = false;
+ }
+
+ if (mPostCommitAnimationInProgress) {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
+ return;
+ }
+
+ if (mBackNavigationInfo == null) {
+ // No focus window found or core are running recents animation, inject back key as
+ // legacy behavior.
+ if (mTriggerBack) {
+ injectBackKey();
+ }
+ finishBackNavigation();
+ return;
+ }
+
+ final int backType = mBackNavigationInfo.getType();
+ // Simply trigger and finish back navigation when no animator defined.
+ if (!shouldDispatchToAnimator() || mActiveCallback == null) {
+ invokeOrCancelBack();
+ return;
+ }
+
+ final BackAnimationRunner runner = mAnimationDefinition.get(backType);
+ if (runner.isWaitingAnimation()) {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+ return;
+ }
+ startPostCommitAnimation();
+ }
+
+ /**
+ * Start the phase 2 animation when gesture is released.
+ * Callback to {@link #onBackAnimationFinished} when it is finished or timeout.
+ */
+ private void startPostCommitAnimation() {
+ if (mPostCommitAnimationInProgress) {
+ return;
+ }
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
+ mPostCommitAnimationInProgress = true;
+ mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
+
+ // The next callback should be {@link #onBackAnimationFinished}.
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(mActiveCallback);
+ } else {
+ dispatchOnBackCancelled(mActiveCallback);
+ }
+ }
+
+ /**
+ * Called when the post commit animation is completed or timeout.
+ * This will trigger the real {@link IOnBackInvokedCallback} behavior.
+ */
+ @VisibleForTesting
+ void onBackAnimationFinished() {
+ if (!mPostCommitAnimationInProgress) {
+ return;
+ }
+ // Stop timeout runner.
+ mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
+ mPostCommitAnimationInProgress = false;
+
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
+
+ // Trigger the real back.
+ invokeOrCancelBack();
+ }
+
+ /**
+ * This should be called after the whole back navigation is completed.
+ */
+ @VisibleForTesting
+ void finishBackNavigation() {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
+ mShouldStartOnNextMoveEvent = false;
+ mTouchTracker.reset();
+ mActiveCallback = null;
+ if (mBackNavigationInfo != null) {
+ mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
+ mBackNavigationInfo = null;
+ }
+ mTriggerBack = false;
}
private void createAdapter() {
@@ -662,22 +618,20 @@
mBackAnimationFinishedCallback = finishedCallback;
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
- runner.startAnimation(apps, wallpapers, nonApps,
- BackAnimationController.this::onBackAnimationFinished);
+ runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
+ BackAnimationController.this::onBackAnimationFinished));
+
if (apps.length >= 1) {
- final int backType = mBackNavigationInfo.getType();
- IOnBackInvokedCallback targetCallback = mAnimationDefinition.get(backType)
- .getCallback();
dispatchOnBackStarted(
- targetCallback, mTouchTracker.createStartEvent(apps[0]));
+ mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
}
if (!mBackGestureStarted) {
// if the down -> up gesture happened before animation start, we have to
// trigger the uninterruptible transition to finish the back animation.
- final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
- startTransition();
- runner.consumeIfGestureFinished(backFinish);
+ final BackEvent backFinish = mTouchTracker.createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ startPostCommitAnimation();
}
});
}
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 c53fcfc..d70b8f5 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
@@ -18,12 +18,12 @@
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import android.annotation.NonNull;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
-import android.window.BackEvent;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
@@ -38,11 +38,11 @@
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
- private boolean mTriggerBack;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
- BackAnimationRunner(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
+ BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner) {
mCallback = callback;
mRunner = runner;
}
@@ -83,25 +83,7 @@
mWaitingAnimation = true;
}
- boolean onGestureFinished(boolean triggerBack) {
- if (mWaitingAnimation) {
- mTriggerBack = triggerBack;
- return true;
- }
- return false;
- }
-
- void consumeIfGestureFinished(final BackEvent backFinish) {
- Log.d(TAG, "Start transition due to gesture is finished");
- try {
- mCallback.onBackProgressed(backFinish);
- if (mTriggerBack) {
- mCallback.onBackInvoked();
- } else {
- mCallback.onBackCancelled();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "dispatch error: ", e);
- }
+ boolean isWaitingAnimation() {
+ return mWaitingAnimation;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
deleted file mode 100644
index 6d72d9c..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
+++ /dev/null
@@ -1,78 +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.wm.shell.back;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.transition.Transitions;
-
-class BackTransitionHandler implements Transitions.TransitionHandler {
- private BackAnimationController mBackAnimationController;
- private WindowContainerToken mDepartingWindowContainerToken;
-
- BackTransitionHandler(@NonNull BackAnimationController backAnimationController) {
- mBackAnimationController = backAnimationController;
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mDepartingWindowContainerToken != null) {
- final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken);
- if (change == null) {
- return false;
- }
-
- startTransaction.hide(change.getLeash());
- startTransaction.apply();
- mDepartingWindowContainerToken = null;
- mBackAnimationController.finishTransition(finishCallback);
- return true;
- }
-
- return false;
- }
-
- @Nullable
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- return null;
- }
-
- @Override
- public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- }
-
- void setDepartingWindowContainerToken(
- @Nullable WindowContainerToken departingWindowContainerToken) {
- mDepartingWindowContainerToken = departingWindowContainerToken;
- }
-}
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 0000000..1e0f9bc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
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 a400555..1fd91de 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
@@ -709,7 +709,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index bb7c4134..d9b4f47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.graphics.Point;
@@ -38,6 +39,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManagerGlobal;
import androidx.annotation.VisibleForTesting;
@@ -112,7 +114,7 @@
}
if (mDisplayController.getDisplayLayout(displayId).rotation()
!= pd.mRotation && isImeShowing(displayId)) {
- pd.startAnimation(true, false /* forceRestart */);
+ pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
}
}
@@ -244,7 +246,7 @@
mInsetsState.set(insetsState, true /* copySources */);
if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
- startAnimation(mImeShowing, true /* forceRestart */);
+ startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
}
@@ -280,7 +282,7 @@
!haveSameLeash(mImeSourceControl, imeSourceControl);
if (mAnimation != null) {
if (positionChanged) {
- startAnimation(mImeShowing, true /* forceRestart */);
+ startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
} else {
if (leashChanged) {
@@ -312,21 +314,23 @@
}
@Override
- public void showInsets(int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
- startAnimation(true /* show */, false /* forceRestart */);
+ startAnimation(true /* show */, false /* forceRestart */, statsToken);
}
@Override
- public void hideInsets(int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
- startAnimation(false /* show */, false /* forceRestart */);
+ startAnimation(false /* show */, false /* forceRestart */, statsToken);
}
@Override
@@ -367,9 +371,11 @@
.navBarFrameHeight();
}
- private void startAnimation(final boolean show, final boolean forceRestart) {
+ private void startAnimation(final boolean show, final boolean forceRestart,
+ @Nullable ImeTracker.Token statsToken) {
final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
if (imeSource == null || mImeSourceControl == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
final Rect newFrame = imeSource.getFrame();
@@ -390,8 +396,9 @@
+ (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
: (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
}
- if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)
+ if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
|| (mAnimationDirection == DIRECTION_HIDE && !show)) {
+ ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
boolean seek = false;
@@ -435,8 +442,11 @@
mTransactionPool.release(t);
});
mAnimation.setInterpolator(INTERPOLATOR);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
+ @Nullable
+ private final ImeTracker.Token mStatsToken = statsToken;
@Override
public void onAnimationStart(Animator animation) {
@@ -455,6 +465,8 @@
: 1.f;
t.setAlpha(mImeSourceControl.getLeash(), alpha);
if (mAnimationDirection == DIRECTION_SHOW) {
+ ImeTracker.get().onProgress(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.show(mImeSourceControl.getLeash());
}
t.apply();
@@ -476,8 +488,16 @@
}
dispatchEndPositioning(mDisplayId, mCancelled, t);
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
+ ImeTracker.get().onProgress(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.hide(mImeSourceControl.getLeash());
removeImeSurface();
+ ImeTracker.get().onHidden(mStatsToken);
+ } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
+ ImeTracker.get().onShown(mStatsToken);
+ } else if (mCancelled) {
+ ImeTracker.get().onCancelled(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
}
t.apply();
mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 8d4a09d..8759301 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.RemoteException;
import android.util.Slog;
@@ -25,6 +26,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import androidx.annotation.BinderThread;
@@ -156,23 +158,29 @@
}
}
- private void showInsets(int types, boolean fromIme) {
+ private void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
- listener.showInsets(types, fromIme);
+ listener.showInsets(types, fromIme, statsToken);
}
}
- private void hideInsets(int types, boolean fromIme) {
+ private void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
- listener.hideInsets(types, fromIme);
+ listener.hideInsets(types, fromIme, statsToken);
}
}
@@ -214,16 +222,18 @@
}
@Override
- public void showInsets(int types, boolean fromIme) throws RemoteException {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.showInsets(types, fromIme);
+ PerDisplay.this.showInsets(types, fromIme, statsToken);
});
}
@Override
- public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.hideInsets(types, fromIme);
+ PerDisplay.this.hideInsets(types, fromIme, statsToken);
});
}
}
@@ -263,15 +273,21 @@
*
* @param types {@link InsetsType} to show
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME show request
+ * or {@code null} otherwise.
*/
- default void showInsets(@InsetsType int types, boolean fromIme) {}
+ default void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {}
/**
* Called when a set of insets source window should be hidden by policy.
*
* @param types {@link InsetsType} to hide
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME hide request
+ * or {@code null} otherwise.
*/
- default void hideInsets(@InsetsType int types, boolean fromIme) {}
+ default void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index e270edb..af13bf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Region;
@@ -46,6 +47,7 @@
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -351,10 +353,10 @@
InsetsSourceControl[] activeControls) {}
@Override
- public void showInsets(int types, boolean fromIme) {}
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
@Override
- public void hideInsets(int types, boolean fromIme) {}
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
@Override
public void moved(int newX, int newY) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 8bc16bc..1474754 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -46,8 +46,10 @@
import androidx.annotation.Nullable;
import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
/**
* Divider for multi window splits.
@@ -364,8 +366,11 @@
mViewHost.relayout(lp);
}
- void setInteractive(boolean interactive) {
+ void setInteractive(boolean interactive, String from) {
if (interactive == mInteractive) return;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
+ from);
mInteractive = interactive;
releaseTouching();
mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 74f8bf9..6e116b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -48,6 +48,7 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.SurfaceUtils;
import java.util.function.Consumer;
@@ -74,10 +75,14 @@
private boolean mShown;
private boolean mIsResizing;
- private Rect mBounds = new Rect();
+ private final Rect mBounds = new Rect();
+ private final Rect mResizingBounds = new Rect();
+ private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
private int mIconSize;
+ private int mOffsetX;
+ private int mOffsetY;
public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
SurfaceSession surfaceSession) {
@@ -158,7 +163,8 @@
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- Rect sideBounds, SurfaceControl.Transaction t) {
+ Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
+ boolean immediately) {
if (mResizingIconView == null) {
return;
}
@@ -167,11 +173,14 @@
mIsResizing = true;
mBounds.set(newBounds);
}
+ mResizingBounds.set(newBounds);
+ mOffsetX = offsetX;
+ mOffsetY = offsetY;
final boolean show =
newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
- final boolean animate = show != mShown;
- if (animate && mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ final boolean update = show != mShown;
+ if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
// background and icon surfaces are non null for next animation.
mFadeAnimator.cancel();
@@ -184,7 +193,7 @@
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
- if (mGapBackgroundLeash == null) {
+ if (mGapBackgroundLeash == null && !immediately) {
final boolean isLandscape = newBounds.height() == sideBounds.height();
final int left = isLandscape ? mBounds.width() : 0;
final int top = isLandscape ? 0 : mBounds.height();
@@ -213,19 +222,50 @@
newBounds.width() / 2 - mIconSize / 2,
newBounds.height() / 2 - mIconSize / 2);
- if (animate) {
- startFadeAnimation(show, null /* finishedConsumer */);
+ if (update) {
+ if (immediately) {
+ t.setVisibility(mBackgroundLeash, show);
+ t.setVisibility(mIconLeash, show);
+ } else {
+ startFadeAnimation(show, null /* finishedConsumer */);
+ }
mShown = show;
}
}
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t) {
+ if (!mShown && mIsResizing) {
+ mTempRect.set(mResizingBounds);
+ mTempRect.offsetTo(-mOffsetX, -mOffsetY);
+ final SurfaceControl screenshot = ScreenshotUtils.takeScreenshot(t,
+ mHostLeash, mTempRect, Integer.MAX_VALUE - 1);
+
+ final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+ final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
+ va.addUpdateListener(valueAnimator -> {
+ final float progress = (float) valueAnimator.getAnimatedValue();
+ animT.setAlpha(screenshot, progress);
+ animT.apply();
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
+ animT.remove(screenshot);
+ animT.apply();
+ animT.close();
+ }
+ });
+ va.start();
+ }
+
if (mResizingIconView == null) {
return;
}
mIsResizing = false;
+ mOffsetX = 0;
+ mOffsetY = 0;
if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
if (!mShown) {
// If fade-out animation is running, just add release callback to it.
@@ -285,10 +325,12 @@
@Override
public void onAnimationStart(@NonNull Animator animation) {
if (show) {
- animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
- } else {
- animT.hide(mGapBackgroundLeash).apply();
+ animT.show(mBackgroundLeash).show(mIconLeash);
}
+ if (mGapBackgroundLeash != null) {
+ animT.setVisibility(mGapBackgroundLeash, show);
+ }
+ animT.apply();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 295a2e3..ec9e6f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -83,8 +83,8 @@
private static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_SWITCH_DURATION = 350;
- private static final int FLING_ENTER_DURATION = 350;
- private static final int FLING_EXIT_DURATION = 350;
+ private static final int FLING_ENTER_DURATION = 450;
+ private static final int FLING_EXIT_DURATION = 450;
private int mDividerWindowWidth;
private int mDividerInsets;
@@ -448,7 +448,8 @@
*/
void updateDivideBounds(int position) {
updateBounds(position);
- mSplitLayoutHandler.onLayoutSizeChanging(this);
+ mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
+ mSurfaceEffectPolicy.mParallaxOffset.y);
}
void setDividePosition(int position, boolean applyLayoutChange) {
@@ -600,7 +601,7 @@
animator.start();
}
- /** Swich both surface position with animation. */
+ /** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
@@ -811,7 +812,7 @@
* @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
* SurfaceControl, SurfaceControl, boolean)
*/
- void onLayoutSizeChanging(SplitLayout layout);
+ void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY);
/**
* Calls when finish resizing the split bounds.
@@ -1092,7 +1093,8 @@
// ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
// because DividerView won't receive onImeVisibilityChanged callback after it being
// re-inflated.
- mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus);
+ mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus,
+ "onImeStartPositioning");
return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@@ -1118,7 +1120,7 @@
// Restore the split layout when wm-shell is not controlling IME insets anymore.
if (!controlling && mImeShown) {
reset();
- mSplitWindowManager.setInteractive(true);
+ mSplitWindowManager.setInteractive(true, "onImeControlTargetChanged");
mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 7fea237..5397552 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -167,9 +167,9 @@
}
}
- void setInteractive(boolean interactive) {
+ void setInteractive(boolean interactive, String from) {
if (mDividerView == null) return;
- mDividerView.setInteractive(interactive);
+ mDividerView.setInteractive(interactive, from);
}
View getDividerView() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 1977dcb..962be9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -260,13 +260,12 @@
ShellInit shellInit,
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler,
- Transitions transitions
+ @ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context, transitions));
+ backgroundHandler, context));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 4459f57..f1670cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,7 +27,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -272,12 +271,11 @@
TaskStackListenerImpl taskStackListener,
UiEventLogger uiEventLogger,
InteractionJankMonitor jankMonitor,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
- uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler);
+ uiEventLogger, mainExecutor, mainHandler);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 34ff6d8..abc4024 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -16,8 +16,11 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -151,21 +154,18 @@
int displayId = mContext.getDisplayId();
+ ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
+
WindowContainerTransaction wct = new WindowContainerTransaction();
- // Reset freeform windowing mode that is set per task level (tasks should inherit
- // container value)
- wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
- true /* transfer */);
- int targetWindowingMode;
+ // Reset freeform windowing mode that is set per task level so tasks inherit it
+ clearFreeformForStandardTasks(runningTasks, wct);
if (active) {
- targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ moveHomeBehindVisibleTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
} else {
- targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
- // Clear any resized bounds
- wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
- true /* transfer */);
+ clearBoundsForStandardTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
}
- prepareWindowingModeChange(wct, displayId, targetWindowingMode);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
} else {
@@ -173,17 +173,69 @@
}
}
- private void prepareWindowingModeChange(WindowContainerTransaction wct,
- int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
- DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
- .getDisplayAreaInfo(displayId);
+ private WindowContainerTransaction clearBoundsForStandardTasks(
+ ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ }
+
+ private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
+ RunningTaskInfo homeTask = null;
+ ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ homeTask = taskInfo;
+ } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && taskInfo.isVisible()) {
+ visibleTasks.add(taskInfo);
+ }
+ }
+ if (homeTask == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
+ } else {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
+ visibleTasks.size());
+ wct.reorder(homeTask.getToken(), true /* onTop */);
+ for (RunningTaskInfo task : visibleTasks) {
+ wct.reorder(task.getToken(), true /* onTop */);
+ }
+ }
+ }
+
+ private void setDisplayAreaWindowingMode(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
+ DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ displayId);
if (displayAreaInfo == null) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE,
"unable to update windowing mode for display %d display not found", displayId);
return;
}
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
"setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
windowingMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 497a6f6..55378a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -311,7 +311,7 @@
animateSplitContainers(true, null /* animCompleteCallback */);
animateHighlight(target);
}
- } else {
+ } else if (mCurrentTarget.type != target.type) {
// Switching between targets
mDropZoneView1.animateSwitch();
mDropZoneView2.animateSwitch();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index f4888fb..168f6d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -98,9 +98,11 @@
switch (change.getMode()) {
case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
onOpenTransitionReady(change, startT, finishT);
break;
+ case WindowManager.TRANSIT_TO_FRONT:
+ onToFrontTransitionReady(change, startT, finishT);
+ break;
case WindowManager.TRANSIT_CLOSE: {
taskInfoList.add(change.getTaskInfo());
onCloseTransitionReady(change, startT, finishT);
@@ -138,6 +140,21 @@
change.getTaskInfo(), startT, finishT);
}
+ private void onToFrontTransitionReady(
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ boolean exists = mWindowDecorViewModel.setupWindowDecorationForTransition(
+ change.getTaskInfo(),
+ startT,
+ finishT);
+ if (!exists) {
+ // Window caption does not exist, create it
+ mWindowDecorViewModel.createWindowDecoration(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
+ }
+ }
+
@Override
public void onTransitionStarting(@NonNull IBinder transition) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index b5ed509..b310ee2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -34,7 +34,6 @@
import android.os.Binder;
import android.util.Slog;
import android.view.ContextThemeWrapper;
-import android.view.Display;
import android.view.IWindow;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -48,7 +47,6 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
@@ -69,14 +67,11 @@
private SurfaceControl mLeash;
private View mBackgroundView;
private @OneHandedState.State int mCurrentState;
- private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
- public BackgroundWindowManager(Context context,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+ public BackgroundWindowManager(Context context) {
super(context.getResources().getConfiguration(), null /* rootSurface */,
null /* hostInputToken */);
mContext = context;
- mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
mTransactionFactory = SurfaceControl.Transaction::new;
}
@@ -117,7 +112,6 @@
.setOpaque(true)
.setName(TAG)
.setCallsite("BackgroundWindowManager#attachToParentSurface");
- mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder);
mLeash = builder.build();
b.setParent(mLeash);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index ad135d1..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -47,7 +47,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -205,14 +204,12 @@
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
ShellExecutor mainExecutor, Handler mainHandler) {
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
OneHandedState oneHandedState = new OneHandedState();
- BackgroundWindowManager backgroundWindowManager =
- new BackgroundWindowManager(context, rootDisplayAreaOrganizer);
+ BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
settingsUtil, windowManager, backgroundWindowManager);
OneHandedAnimationController animationController =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index f707363..0006244 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -23,7 +23,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.RemoteAction;
@@ -74,11 +73,6 @@
void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
/**
- * Wait until the next frame to run the given Runnable.
- */
- void runWithNextFrame(@NonNull Runnable runnable);
-
- /**
* Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
* need to synchronize the movements on the same frame as PiP.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 2d7c5ce..f170e77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -179,10 +179,8 @@
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResizeDelayedIfNeeded(() -> {
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
- });
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
}
}
@@ -198,34 +196,6 @@
}
};
- /**
- * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
- *
- * This is done to avoid a race condition between the last transaction applied in
- * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a
- * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it
- * gets applied before the transaction created by the last onAnimationUpdate. As a result of
- * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which
- * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize
- * operation until the next frame. This aligns the last onAnimationUpdate transaction with the
- * WCT application.
- *
- * The race only happens when the PiP surface transaction has to be synced with the PiP menu
- * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and
- * the PiP menu contents.
- */
- private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
- if (!shouldSyncPipTransactionWithMenu()) {
- finishResizeRunnable.run();
- return;
- }
- mPipMenuController.runWithNextFrame(finishResizeRunnable);
- }
-
- private boolean shouldSyncPipTransactionWithMenu() {
- return mPipMenuController.isMenuVisible();
- }
-
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -251,7 +221,7 @@
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (shouldSyncPipTransactionWithMenu()) {
+ if (mPipMenuController.isMenuVisible()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1253,7 +1223,7 @@
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (shouldSyncPipTransactionWithMenu()) {
+ if (mPipMenuController.isMenuVisible()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1295,7 +1265,7 @@
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (shouldSyncPipTransactionWithMenu()) {
+ if (mPipMenuController.isMenuVisible()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 531b9de..a0a8f9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -305,18 +305,6 @@
showResizeHandle);
}
- @Override
- public void runWithNextFrame(Runnable runnable) {
- if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
- runnable.run();
- }
-
- mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
- mMainHandler.post(runnable);
- });
- mPipMenuView.invalidate();
- }
-
/**
* Move the PiP menu, which does a translation and possibly a scale transformation.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 7365b95..1f7a7fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -29,6 +29,7 @@
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
import androidx.annotation.BinderThread;
@@ -362,6 +363,15 @@
}
@Override
+ public void takeScreenshotOfWindow(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+ // AbstractAccessibilityServiceConnection uses the standard
+ // IAccessibilityInteractionConnection for takeScreenshotOfWindow for Pip windows,
+ // so do nothing here.
+ }
+
+ @Override
public void clearAccessibilityFocus() throws RemoteException {
// Do nothing
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index c39400a..ab7edbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -421,18 +421,6 @@
}
@Override
- public void runWithNextFrame(Runnable runnable) {
- if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
- runnable.run();
- }
-
- mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
- mMainHandler.post(runnable);
- });
- mPipMenuView.invalidate();
- }
-
- @Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
Rect pipDestBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index c52ed24..75f9a4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -42,8 +42,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
- Consts.TAG_WM_SHELL),
+ WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SPLIT_SCREEN),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -110,6 +110,7 @@
private static class Consts {
private static final String TAG_WM_SHELL = "WindowManagerShell";
private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
+ private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index eb08d0e..56aa742 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -86,8 +86,8 @@
/**
* Starts a pair of intent and task in one transition.
*/
- oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
- in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+ in Bundle options2, int sidePosition, float splitRatio,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/**
@@ -95,7 +95,7 @@
*/
oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
in Bundle options2, int splitPosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
@@ -108,9 +108,8 @@
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 12;
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
/**
* Starts a pair of shortcut and task using legacy transition system.
@@ -120,6 +119,21 @@
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
/**
+ * Start a pair of intents using legacy transition system.
+ */
+ oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
+ in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in InstanceId instanceId) = 18;
+
+ /**
+ * Start a pair of intents in one transition.
+ */
+ oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
+ in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
+ float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+
+ /**
* Blocking call that notifies and gets additional split-screen targets when entering
* recents (for example: the dividerBar).
* @param appTargets apps that will be re-parented to display area
@@ -133,4 +147,4 @@
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
}
-// Last id = 17
+// Last id = 19
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c6a2b83..1774dd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,6 +33,8 @@
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -60,13 +63,12 @@
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -166,8 +168,11 @@
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+ private final String[] mMultiInstancesComponents;
- private StageCoordinator mStageCoordinator;
+ @VisibleForTesting
+ StageCoordinator mStageCoordinator;
+
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +215,51 @@
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
+
+ // TODO(255224696): Remove the config once having a way for client apps to opt-in
+ // multi-instances split.
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+ }
+
+ @VisibleForTesting
+ SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ RecentTasksController recentTasks,
+ ShellExecutor mainExecutor,
+ StageCoordinator stageCoordinator) {
+ mShellCommandHandler = shellCommandHandler;
+ mShellController = shellController;
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = Optional.of(recentTasks);
+ mStageCoordinator = stageCoordinator;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+ shellInit.addInitCallback(this::onInit, this);
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
@@ -471,72 +521,140 @@
startIntent(intent, fillInIntent, position, options);
}
+ private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
+ private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Intent fillInIntent1 = null;
+ Intent fillInIntent2 = null;
+ if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)
+ && supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
+ pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
+ instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- // Flag this as a no-user-action launch to prevent sending user leaving event to the
- // current top activity since it's going to be put into another side of the split. This
- // prevents the current top activity from going into pip mode due to user leaving event.
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split and there is no reusable background task.
- if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
- ? mRecentTasksOptional.get().findTaskInBackground(
- intent.getIntent().getComponent())
- : null;
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
+ if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+ final ComponentName launching = intent.getIntent().getComponent();
+ if (supportMultiInstancesSplit(launching)) {
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background with priority.
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+ // the split and there is no reusable background task.
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startIntent");
return;
}
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
- if (!ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
/** Returns {@code true} if it's launching the same component on both sides of the split. */
- @VisibleForTesting
- boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
- if (startIntent == null) {
- return false;
- }
+ private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
+ @SplitPosition int position, int taskId) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
- final ComponentName launchingActivity = startIntent.getComponent();
- if (launchingActivity == null) {
- return false;
- }
+ final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
+ if (launchingActivity == null) return false;
- if (isSplitScreenVisible()) {
- // To prevent users from constantly dropping the same app to the same side resulting in
- // a large number of instances in the background.
- final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
- final ComponentName targetActivity = targetTaskInfo != null
- ? targetTaskInfo.baseIntent.getComponent() : null;
- if (Objects.equals(launchingActivity, targetActivity)) {
- return false;
+ if (taskId != INVALID_TASK_ID) {
+ final ActivityManager.RunningTaskInfo taskInfo =
+ mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (taskInfo != null) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
}
-
- // Allow users to start a new instance the same to adjacent side.
- final ActivityManager.RunningTaskInfo pairedTaskInfo =
- getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity = pairedTaskInfo != null
- ? pairedTaskInfo.baseIntent.getComponent() : null;
- return Objects.equals(launchingActivity, pairedActivity);
+ return false;
}
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ if (!isSplitScreenVisible()) {
+ // Split screen is not yet activated, check if the current top running task is valid to
+ // split together.
+ final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
+ if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ }
+ return false;
+ }
+
+ // Compare to the adjacent side of the split to determine if this is launching the same
+ // component adjacently.
+ final ActivityManager.RunningTaskInfo pairedTaskInfo =
+ getTaskInfo(SplitLayout.reversePosition(position));
+ final ComponentName pairedActivity = pairedTaskInfo != null
+ ? pairedTaskInfo.baseIntent.getComponent() : null;
+ return Objects.equals(launchingActivity, pairedActivity);
+ }
+
+ private boolean launchSameComponentAdjacently(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2) {
+ return Objects.equals(pendingIntent1.getIntent().getComponent(),
+ pendingIntent2.getIntent().getComponent());
+ }
+
+ @VisibleForTesting
+ /** Returns {@code true} if the component supports multi-instances split. */
+ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+ if (launching == null) return false;
+
+ final String componentName = launching.flattenToString();
+ for (int i = 0; i < mMultiInstancesComponents.length; i++) {
+ if (mMultiInstancesComponents[i].equals(componentName)) {
+ return true;
+ }
}
return false;
@@ -839,14 +957,13 @@
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
- int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, options1, taskId, options2,
- splitPosition, splitRatio, adapter, instanceId));
+ controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId));
}
@Override
@@ -872,14 +989,13 @@
}
@Override
- public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
- (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
- fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
- remoteTransition, instanceId));
+ (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
}
@Override
@@ -894,6 +1010,27 @@
}
@Override
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
+ (controller) ->
+ controller.startIntentsWithLegacyTransition(
+ pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId)
+ );
+ }
+
+ @Override
+ public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ // TODO(b/259368992): To be implemented.
+ }
+
+ @Override
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 15a1133..acb71a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,7 +24,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -170,6 +169,7 @@
private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
private boolean mKeyguardShowing;
+ private boolean mShowDecorImmediately;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -428,6 +428,11 @@
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, position, options);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -441,13 +446,7 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(transitType, wct, null, this,
- aborted -> {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
- } /* consumedCallback */,
+ null /* consumedCallback */,
(finishWct, finishT) -> {
if (!evictWct.isEmpty()) {
finishWct.merge(evictWct, true);
@@ -457,7 +456,7 @@
/** Launches an activity into split by legacy transition. */
void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -473,12 +472,6 @@
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
mSplitUnsupportedToast.show();
- } else {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- getSideStagePosition()), null);
- }
}
// Do nothing when the animation was cancelled.
@@ -596,8 +589,7 @@
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
addActivityOptions(options1, mSideStage);
@@ -607,7 +599,20 @@
instanceId);
}
- /** Starts a pair of intent and task using legacy transition. */
+ /** Starts a pair of intents using legacy transition. */
+ void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId);
+ }
+
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
@@ -635,12 +640,29 @@
instanceId);
}
+ private void startWithLegacyTransition(WindowContainerTransaction wct,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
+ mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
+ null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
+ instanceId);
+ }
+
/**
* @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
// Init divider first to make divider leash for remote animation target.
@@ -709,7 +731,11 @@
if (mainOptions == null) mainOptions = new Bundle();
addActivityOptions(mainOptions, mMainStage);
updateWindowBounds(mSplitLayout, wct);
- wct.startTask(mainTaskId, mainOptions);
+ if (mainTaskId == INVALID_TASK_ID) {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ }
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
@@ -771,9 +797,8 @@
mSideStage.evictInvisibleChildren(wct);
}
- Bundle resolveStartStage(@StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +869,8 @@
: mMainStage.getTopVisibleChildTaskId();
}
- void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
- if (mSideStagePosition == sideStagePosition) return;
- SurfaceControl.Transaction t = mTransactionPool.acquire();
+ void switchSplitPosition(String reason) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +910,11 @@
va.start();
});
});
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1097,7 +1126,7 @@
activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
} catch (RemoteException | NullPointerException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Unable to update focus on the chosen stage, %s", TAG, e);
+ "Unable to update focus on the chosen stage: %s", e.getMessage());
}
}
@@ -1434,14 +1463,14 @@
}
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Request to %s divider bar from %s.", TAG,
+ "Request to %s divider bar from %s.",
(visible ? "show" : "hide"), Debug.getCaller());
// Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
// dismissing animation.
if (visible && mKeyguardShowing) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Defer showing divider bar due to keyguard showing.", TAG);
+ " Defer showing divider bar due to keyguard showing.");
return;
}
@@ -1450,7 +1479,7 @@
if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ " Skip animating divider bar due to it's remote animating.");
return;
}
@@ -1465,12 +1494,12 @@
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to divider leash not ready.", TAG);
+ " Skip animating divider bar due to divider leash not ready.");
return;
}
if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ " Skip animating divider bar due to it's remote animating.");
return;
}
@@ -1561,6 +1590,7 @@
if (mLogger.isEnterRequestedByDrag()) {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
} else {
+ mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
@@ -1617,10 +1647,7 @@
@Override
public void onDoubleTappedDivider() {
- setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ switchSplitPosition("double tap");
}
@Override
@@ -1633,20 +1660,22 @@
}
@Override
- public void onLayoutSizeChanging(SplitLayout layout) {
+ public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) {
final SurfaceControl.Transaction t = mTransactionPool.acquire();
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t);
- mSideStage.onResizing(mTempRect2, mTempRect1, t);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
@Override
public void onLayoutSizeChanged(SplitLayout layout) {
+ // Reset this flag every time onLayoutSizeChanged.
+ mShowDecorImmediately = false;
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6b90eab..bcf900b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -288,9 +288,11 @@
}
}
- void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t) {
+ void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
+ int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
- mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t);
+ mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
+ offsetY, immediately);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index a0e176c..ff6f2b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -430,7 +430,8 @@
}
@Override
- public @Nullable SplashScreenView get() {
+ @Nullable
+ public SplashScreenView get() {
synchronized (this) {
while (!mIsViewSet) {
try {
@@ -691,7 +692,7 @@
private final TaskSnapshotWindow mTaskSnapshotWindow;
private SplashScreenView mContentView;
private boolean mSetSplashScreen;
- private @StartingWindowType int mSuggestType;
+ @StartingWindowType private int mSuggestType;
private int mBGColor;
private final long mCreateTime;
private int mSystemBarAppearance;
@@ -732,7 +733,7 @@
// Reset the system bar color which set by splash screen, make it align to the app.
private void clearSystemBarColor() {
- if (mDecorView == null) {
+ if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
return;
}
if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 928e71f..63d4a6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -41,6 +41,7 @@
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -395,6 +396,11 @@
}
}
+ // The back gesture has animated this change before transition happen, so here we don't
+ // play the animation again.
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ continue;
+ }
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
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 519ec14..56d51bd 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
@@ -24,7 +24,6 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
-import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -336,9 +335,12 @@
boolean isOpening = isOpeningType(info.getType());
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if ((change.getFlags() & TransitionInfo.FLAG_IS_SYSTEM_WINDOW) != 0) {
+ if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
// Currently system windows are controlled by WindowState, so don't change their
- // surfaces. Otherwise their window tokens could be hidden unexpectedly.
+ // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly.
+ // This includes Wallpaper (always z-ordered at bottom) and IME (associated with
+ // app), because there may not be a transition associated with their visibility
+ // changes, and currently they don't need transition animation.
continue;
}
final SurfaceControl leash = change.getLeash();
@@ -375,16 +377,7 @@
finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- // Wallpaper/IME are anomalies: their visibility is tied to other WindowStates.
- // As a result, we actually can't hide their WindowTokens because there may not be a
- // transition associated with them becoming visible again. Fortunately, since
- // wallpapers are always z-ordered to the back, we don't have to worry about it
- // flickering to the front during reparenting. Similarly, the IME is reparented to
- // the associated app, so its visibility is coupled. So, an explicit hide is not
- // needed visually anyways.
- if ((change.getFlags() & (FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD)) == 0) {
- finishT.hide(leash);
- }
+ finishT.hide(leash);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 36dd8ed..8369569 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -44,6 +44,8 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -71,6 +73,7 @@
private DesktopModeController mDesktopModeController;
private EventReceiver mEventReceiver;
private InputMonitor mInputMonitor;
+ private boolean mTransitionDragActive;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
@@ -91,6 +94,7 @@
mDisplayController = displayController;
mSyncQueue = syncQueue;
mDesktopModeController = desktopModeController;
+ mTransitionDragActive = false;
}
@Override
@@ -105,6 +109,11 @@
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
if (!shouldShowWindowDecor(taskInfo)) return false;
+ CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (oldDecoration != null) {
+ // close the old decoration if it exists to avoid two window decorations being added
+ oldDecoration.close();
+ }
final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
mContext,
mDisplayController,
@@ -119,7 +128,8 @@
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
mDragStartListener);
CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, taskPositioner);
+ new CaptionTouchEventListener(taskInfo, taskPositioner,
+ windowDecoration.getDragDetector());
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
setupWindowDecorationForTransition(taskInfo, startT, finishT);
@@ -141,23 +151,25 @@
}
@Override
- public void setupWindowDecorationForTransition(
+ public boolean setupWindowDecorationForTransition(
RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
- if (decoration == null) return;
+ if (decoration == null) return false;
decoration.relayout(taskInfo, startT, finishT);
+ return true;
}
@Override
- public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ public boolean destroyWindowDecoration(RunningTaskInfo taskInfo) {
final CaptionWindowDecoration decoration =
mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
- if (decoration == null) return;
+ if (decoration == null) return false;
decoration.close();
+ return true;
}
private class CaptionTouchEventListener implements
@@ -166,16 +178,18 @@
private final int mTaskId;
private final WindowContainerToken mTaskToken;
private final DragResizeCallback mDragResizeCallback;
+ private final DragDetector mDragDetector;
private int mDragPointerId = -1;
- private boolean mDragActive = false;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback) {
+ DragResizeCallback dragResizeCallback,
+ DragDetector dragDetector) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback;
+ mDragDetector = dragDetector;
}
@Override
@@ -224,19 +238,21 @@
@Override
public boolean onTouch(View v, MotionEvent e) {
+ boolean isDrag = false;
int id = v.getId();
if (id != R.id.caption_handle && id != R.id.caption) {
return false;
}
- if (id == R.id.caption_handle || mDragActive) {
+ if (id == R.id.caption_handle) {
+ isDrag = mDragDetector.detectDragEvent(e);
handleEventForMove(e);
}
if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
+ return isDrag;
}
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.isFocused) {
- return false;
+ return isDrag;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mTaskToken, true /* onTop */);
@@ -244,6 +260,10 @@
return true;
}
+ /**
+ * @param e {@link MotionEvent} to process
+ * @return {@code true} if a drag is happening; or {@code false} if it is not
+ */
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
int windowingMode = mDesktopModeController
@@ -252,12 +272,12 @@
return;
}
switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDragActive = true;
- mDragPointerId = e.getPointerId(0);
+ case MotionEvent.ACTION_DOWN: {
+ mDragPointerId = e.getPointerId(0);
mDragResizeCallback.onDragResizeStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
break;
+ }
case MotionEvent.ACTION_MOVE: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragResizeCallback.onDragResizeMove(
@@ -266,14 +286,13 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- mDragActive = false;
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
.stableInsets().top;
mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight
- && windowingMode == WINDOWING_MODE_FREEFORM) {
+ && DesktopModeStatus.isActive(mContext)) {
mDesktopModeController.setDesktopModeActive(false);
}
break;
@@ -291,26 +310,97 @@
@Override
public void onInputEvent(InputEvent event) {
boolean handled = false;
- if (event instanceof MotionEvent
- && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+ if (event instanceof MotionEvent) {
handled = true;
- CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+ CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
}
finishInputEvent(event, handled);
}
}
- // If any input received is outside of caption bounds, turn off handle menu
- private void handleMotionEvent(MotionEvent ev) {
- int size = mWindowDecorByTaskId.size();
- for (int i = 0; i < size; i++) {
- CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
- if (decoration != null) {
- decoration.closeHandleMenuIfNeeded(ev);
+ /**
+ * Handle MotionEvents relevant to focused task's caption that don't directly touch it
+ * @param ev the {@link MotionEvent} received by {@link EventReceiver}
+ */
+ private void handleReceivedMotionEvent(MotionEvent ev) {
+ if (!DesktopModeStatus.isActive(mContext)) {
+ handleCaptionThroughStatusBar(ev);
+ }
+ handleEventOutsideFocusedCaption(ev);
+ // Prevent status bar from reacting to a caption drag.
+ if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
+ mInputMonitor.pilferPointers();
+ }
+ }
+
+ // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
+ private void handleEventOutsideFocusedCaption(MotionEvent ev) {
+ int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ CaptionWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ return;
+ }
+
+ if (!mTransitionDragActive) {
+ focusedDecor.closeHandleMenuIfNeeded(ev);
}
}
}
+ /**
+ * Perform caption actions if not able to through normal means.
+ * Turn on desktop mode if handle is dragged below status bar.
+ */
+ private void handleCaptionThroughStatusBar(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Begin drag through status bar if applicable.
+ CaptionWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
+ && focusedDecor.checkTouchEventInHandle(ev)) {
+ mTransitionDragActive = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ CaptionWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ mTransitionDragActive = false;
+ return;
+ }
+ if (mTransitionDragActive) {
+ mTransitionDragActive = false;
+ int statusBarHeight = mDisplayController
+ .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
+ if (ev.getY() > statusBarHeight) {
+ mDesktopModeController.setDesktopModeActive(true);
+ return;
+ }
+ }
+ focusedDecor.checkClickEvent(ev);
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ mTransitionDragActive = false;
+ }
+ }
+ }
+
+ @Nullable
+ private CaptionWindowDecoration getFocusedDecor() {
+ int size = mWindowDecorByTaskId.size();
+ CaptionWindowDecoration focusedDecor = null;
+ for (int i = 0; i < size; i++) {
+ CaptionWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ if (decor != null && decor.isFocused()) {
+ focusedDecor = decor;
+ break;
+ }
+ }
+ return focusedDecor;
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 03cad04..59576cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -23,6 +23,7 @@
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -62,6 +63,8 @@
private boolean mDesktopActive;
+ private DragDetector mDragDetector;
+
private AdditionalWindow mHandleMenu;
CaptionWindowDecoration(
@@ -79,6 +82,7 @@
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mDesktopActive = DesktopModeStatus.isActive(mContext);
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
void setCaptionListeners(
@@ -92,6 +96,10 @@
mDragResizeCallback = dragResizeCallback;
}
+ DragDetector getDragDetector() {
+ return mDragDetector;
+ }
+
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -182,6 +190,8 @@
}
int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+ mDragDetector.setTouchSlop(touchSlop);
+
int resize_handle = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_handle);
int resize_corner = mResult.mRootView.getResources()
@@ -234,7 +244,7 @@
* Sets the visibility of buttons and color of caption based on desktop mode status
*
*/
- public void setButtonVisibility() {
+ void setButtonVisibility() {
mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
@@ -253,7 +263,7 @@
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
- public boolean isHandleMenuActive() {
+ boolean isHandleMenuActive() {
return mHandleMenu != null;
}
@@ -268,7 +278,7 @@
/**
* Create and display handle menu window
*/
- public void createHandleMenu() {
+ void createHandleMenu() {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mDecorWindowContext.getResources();
int x = mRelayoutParams.mCaptionX;
@@ -289,7 +299,7 @@
/**
* Close the handle menu window
*/
- public void closeHandleMenu() {
+ void closeHandleMenu() {
if (!isHandleMenuActive()) return;
mHandleMenu.releaseView();
mHandleMenu = null;
@@ -304,24 +314,85 @@
/**
* Close an open handle menu if input is outside of menu coordinates
* @param ev the tapped point to compare against
- * @return
*/
- public void closeHandleMenuIfNeeded(MotionEvent ev) {
- if (mHandleMenu != null) {
- Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
- .positionInParent;
- final Resources resources = mDecorWindowContext.getResources();
- ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
- ev.offsetLocation(-positionInParent.x, -positionInParent.y);
- int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
- int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
- if (!(ev.getX() >= 0 && ev.getY() >= 0
- && ev.getX() <= width && ev.getY() <= height)) {
+ void closeHandleMenuIfNeeded(MotionEvent ev) {
+ if (isHandleMenuActive()) {
+ if (!checkEventInCaptionView(ev, R.id.caption)) {
closeHandleMenu();
}
}
}
+ boolean isFocused() {
+ return mTaskInfo.isFocused;
+ }
+
+ /**
+ * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
+ * @param ev the {@link MotionEvent} to offset
+ * @return the point of the input in local space
+ */
+ private PointF offsetCaptionLocation(MotionEvent ev) {
+ PointF result = new PointF(ev.getX(), ev.getY());
+ Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+ .positionInParent;
+ result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+ result.offset(-positionInParent.x, -positionInParent.y);
+ return result;
+ }
+
+ /**
+ * Determine if a passed MotionEvent is in a view in caption
+ * @param ev the {@link MotionEvent} to check
+ * @param layoutId the id of the view
+ * @return {@code true} if event is inside the specified view, {@code false} if not
+ */
+ private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
+ if (mResult.mRootView == null) return false;
+ PointF inputPoint = offsetCaptionLocation(ev);
+ View view = mResult.mRootView.findViewById(layoutId);
+ return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
+ }
+
+ boolean checkTouchEventInHandle(MotionEvent ev) {
+ if (isHandleMenuActive()) return false;
+ return checkEventInCaptionView(ev, R.id.caption_handle);
+ }
+
+ /**
+ * Check a passed MotionEvent if a click has occurred on any button on this caption
+ * Note this should only be called when a regular onClick is not possible
+ * (i.e. the button was clicked through status bar layer)
+ * @param ev the MotionEvent to compare
+ */
+ void checkClickEvent(MotionEvent ev) {
+ if (mResult.mRootView == null) return;
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+ PointF inputPoint = offsetCaptionLocation(ev);
+ if (!isHandleMenuActive()) {
+ View handle = caption.findViewById(R.id.caption_handle);
+ clickIfPointInView(inputPoint, handle);
+ } else {
+ View menu = mHandleMenu.mWindowViewHost.getView();
+ View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ if (clickIfPointInView(inputPoint, fullscreen)) return;
+ View desktop = menu.findViewById(R.id.desktop_button);
+ if (clickIfPointInView(inputPoint, desktop)) return;
+ View split = menu.findViewById(R.id.split_screen_button);
+ if (clickIfPointInView(inputPoint, split)) return;
+ View more = menu.findViewById(R.id.more_button);
+ clickIfPointInView(inputPoint, more);
+ }
+ }
+
+ private boolean clickIfPointInView(PointF inputPoint, View v) {
+ if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
+ mOnCaptionButtonClickListener.onClick(v);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void close() {
closeDragResizeListener();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
new file mode 100644
index 0000000..0abe8ab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wm.shell.windowdecor;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * A detector for touch inputs that differentiates between drag and click inputs.
+ * All touch events must be passed through this class to track a drag event.
+ */
+public class DragDetector {
+ private int mTouchSlop;
+ private PointF mInputDownPoint;
+ private boolean mIsDragEvent;
+ private int mDragPointerId;
+ public DragDetector(int touchSlop) {
+ mTouchSlop = touchSlop;
+ mInputDownPoint = new PointF();
+ mIsDragEvent = false;
+ mDragPointerId = -1;
+ }
+
+ /**
+ * Determine if {@link MotionEvent} is part of a drag event.
+ * @return {@code true} if this is a drag event, {@code false} if not
+ */
+ public boolean detectDragEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case ACTION_DOWN: {
+ mDragPointerId = ev.getPointerId(0);
+ float rawX = ev.getRawX(0);
+ float rawY = ev.getRawY(0);
+ mInputDownPoint.set(rawX, rawY);
+ return false;
+ }
+ case ACTION_MOVE: {
+ if (!mIsDragEvent) {
+ int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+ float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
+ float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+ if (Math.hypot(dx, dy) > mTouchSlop) {
+ mIsDragEvent = true;
+ }
+ }
+ return mIsDragEvent;
+ }
+ case ACTION_UP: {
+ boolean result = mIsDragEvent;
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return result;
+ }
+ case ACTION_CANCEL: {
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return false;
+ }
+ }
+ return mIsDragEvent;
+ }
+
+ public void setTouchSlop(int touchSlop) {
+ mTouchSlop = touchSlop;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index b9f16b6..d3f1332 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -22,7 +22,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.content.Context;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -38,6 +37,7 @@
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
import com.android.internal.view.BaseIWindow;
@@ -76,7 +76,7 @@
private Rect mRightBottomCornerBounds;
private int mDragPointerId = -1;
- private int mTouchSlop;
+ private DragDetector mDragDetector;
DragResizeInputListener(
Context context,
@@ -115,6 +115,7 @@
mInputEventReceiver = new TaskResizeInputEventReceiver(
mInputChannel, mHandler, mChoreographer);
mCallback = callback;
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
/**
@@ -146,7 +147,7 @@
mHeight = height;
mResizeHandleThickness = resizeHandleThickness;
mCornerSize = cornerSize;
- mTouchSlop = touchSlop;
+ mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -228,7 +229,6 @@
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
private boolean mDragging;
- private final PointF mActionDownPoint = new PointF();
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -276,7 +276,9 @@
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
-
+ if (isTouch) {
+ mDragging = mDragDetector.detectDragEvent(e);
+ }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
float x = e.getX(0);
@@ -290,7 +292,6 @@
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
- mActionDownPoint.set(rawX, rawY);
int ctrlType = calculateCtrlType(isTouch, x, y);
mCallback.onDragResizeStart(ctrlType, rawX, rawY);
result = true;
@@ -304,14 +305,7 @@
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
- if (isTouch) {
- // Check for touch slop for touch events
- float dx = rawX - mActionDownPoint.x;
- float dy = rawY - mActionDownPoint.y;
- if (!mDragging && Math.hypot(dx, dy) > mTouchSlop) {
- mDragging = true;
- }
- } else {
+ if (!isTouch) {
// For all other types allow immediate dragging.
mDragging = true;
}
@@ -323,14 +317,13 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- if (mDragging) {
+ if (mShouldHandleEvents && mDragging) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
mCallback.onDragResizeEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
}
mDragging = false;
mShouldHandleEvents = false;
- mActionDownPoint.set(0, 0);
mDragPointerId = -1;
result = true;
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index f0f2db7..a49a300 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,9 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mResizeStartPoint = new PointF();
private final Rect mResizeTaskBounds = new Rect();
+ // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
+ // Used to optimized fluid resizing of freeform tasks.
+ private boolean mPendingDragResizeHint = false;
private int mCtrlType;
private DragStartListener mDragStartListener;
@@ -53,6 +56,12 @@
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ if (ctrlType != CTRL_TYPE_UNDEFINED) {
+ // The task is being resized, send the |dragResizing| hint to core with the first
+ // bounds-change wct.
+ mPendingDragResizeHint = true;
+ }
+
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
@@ -63,19 +72,31 @@
@Override
public void onDragResizeMove(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (changeBounds(wct, x, y)) {
+ if (mPendingDragResizeHint) {
+ // This is the first bounds change since drag resize operation started.
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
+ mPendingDragResizeHint = false;
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
}
@Override
public void onDragResizeEnd(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+ changeBounds(wct, x, y);
+ mTaskOrganizer.applyTransaction(wct);
mCtrlType = 0;
mTaskBoundsAtDragStart.setEmpty();
mResizeStartPoint.set(0, 0);
+ mPendingDragResizeHint = false;
}
- private void changeBounds(float x, float y) {
+ private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
float deltaX = x - mResizeStartPoint.x;
mResizeTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
@@ -96,10 +117,10 @@
}
if (!mResizeTaskBounds.isEmpty()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ return true;
}
+ return false;
}
interface DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d7f71c8..2ce4d04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -44,7 +44,7 @@
* @param taskSurface the surface of the task
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
- * @return the window decoration object
+ * @return {@code true} if window decoration was created, {@code false} otherwise
*/
boolean createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
@@ -66,8 +66,9 @@
*
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
+ * @return {@code true} if window decoration exists, {@code false} otherwise
*/
- void setupWindowDecorationForTransition(
+ boolean setupWindowDecorationForTransition(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT);
@@ -76,6 +77,7 @@
* Destroys the window decoration of the give task.
*
* @param taskInfo the info of the task
+ * @return {@code true} if window decoration was destroyed, {@code false} otherwise
*/
- void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
+ boolean destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 2d6e8f5..08913c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -13,6 +13,8 @@
<option name="run-command" value="cmd window tracing level all" />
<!-- set WM tracing to frame (avoid incomplete states) -->
<option name="run-command" value="cmd window tracing frame" />
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
<!-- ensure lock screen mode is swipe -->
<option name="run-command" value="locksettings set-disabled false" />
<!-- restart launcher to activate TAPL -->
@@ -34,4 +36,4 @@
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 2bce8e45..9533b91 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -24,6 +24,8 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.EdgeExtensionComponentMatcher
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -49,6 +51,8 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+ private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+ private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -159,8 +163,18 @@
/** {@inheritDoc} */
@Presubmit
@Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ ignoreLayers = listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ ComponentNameMatcher.IME_SNAPSHOT,
+ EdgeExtensionComponentMatcher(),
+ MagnifierLayer,
+ PopupWindowLayer))
+ }
+ }
/** {@inheritDoc} */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index e9c765a..dcadb5a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -16,8 +16,10 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.IwTest
import android.view.WindowManagerPolicyConstants
import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -75,38 +77,39 @@
}
}
- @Postsubmit
+ @IwTest(focusArea = "sysui")
+ @Presubmit
@Test
fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
fromOtherApp = false, appExistAtStart = false)
- @Postsubmit
+ @Presubmit
@Test
fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
- @Postsubmit
+ @Presubmit
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
- @Postsubmit
+ @Presubmit
@Test
fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
- @Postsubmit
+ @Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = false, portraitPosTop = false)
- @Postsubmit
+ @Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
secondaryApp)
- @Postsubmit
+ @Presubmit
@Test
fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
- @Postsubmit
+ @Presubmit
@Test
fun secondaryAppWindowBecomesVisible() {
testSpec.assertWm {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index ead451f..4a3284e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -41,6 +41,7 @@
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import org.junit.Assert.assertNotNull
import java.util.Collections
internal object SplitScreenUtils {
@@ -50,7 +51,7 @@
private const val DIVIDER_BAR = "docked_divider_handle"
private const val OVERVIEW_SNAPSHOT = "snapshot"
private const val GESTURE_STEP_MS = 16L
- private const val LONG_PRESS_TIME_MS = 100L
+ private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
private val notificationScrollerSelector: BySelector
@@ -275,13 +276,6 @@
}
}
- fun longPress(instrumentation: Instrumentation, point: Point) {
- val downTime = SystemClock.uptimeMillis()
- touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
- SystemClock.sleep(LONG_PRESS_TIME_MS)
- touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
- }
-
fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
val allApps = tapl.workspace.switchToAllApps()
@@ -353,9 +347,11 @@
Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
TIMEOUT_MS
)
- longPress(instrumentation, textView.visibleCenter)
+ assertNotNull("Unable to find the TextView", textView)
+ textView.click(LONG_PRESS_TIME_MS)
val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+ assertNotNull("Unable to find the copy button", copyBtn)
copyBtn.click()
// Paste text to destinationApp
@@ -364,9 +360,11 @@
Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
TIMEOUT_MS
)
- longPress(instrumentation, editText.visibleCenter)
+ assertNotNull("Unable to find the EditText", editText)
+ editText.click(LONG_PRESS_TIME_MS)
val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+ assertNotNull("Unable to find the paste button", pasteBtn)
pasteBtn.click()
// Verify text
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index d84954d..f5f5fd8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -73,7 +73,8 @@
}
}
- @Postsubmit
+ @IwTest(focusArea = "sysui")
+ @Presubmit
@Test
fun cujCompleted() {
testSpec.appWindowIsVisibleAtStart(thirdApp)
@@ -87,7 +88,7 @@
testSpec.splitScreenDividerIsVisibleAtEnd()
}
- @Postsubmit
+ @Presubmit
@Test
fun splitScreenDividerInvisibleAtMiddle() =
testSpec.assertLayers {
@@ -114,7 +115,7 @@
@Test
fun fourthAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(fourthApp)
- @Postsubmit
+ @Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
testSpec.splitAppLayerBoundsIsVisibleAtEnd(
@@ -123,7 +124,7 @@
portraitPosTop = false
)
- @Postsubmit
+ @Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
testSpec.splitAppLayerBoundsIsVisibleAtEnd(
@@ -132,7 +133,7 @@
portraitPosTop = true
)
- @Postsubmit
+ @Presubmit
@Test
fun thirdAppBoundsIsVisibleAtBegin() =
testSpec.assertLayersStart {
@@ -144,7 +145,7 @@
)
}
- @Postsubmit
+ @Presubmit
@Test
fun fourthAppBoundsIsVisibleAtBegin() =
testSpec.assertLayersStart {
@@ -156,19 +157,19 @@
)
}
- @Postsubmit
+ @Presubmit
@Test
fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
- @Postsubmit
+ @Presubmit
@Test
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
- @Postsubmit
+ @Presubmit
@Test
fun thirdAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(thirdApp)
- @Postsubmit
+ @Presubmit
@Test
fun fourthAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(fourthApp)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7cbace5..081c8ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,13 +16,9 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +30,6 @@
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.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -44,11 +38,9 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -61,8 +53,6 @@
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -638,130 +628,10 @@
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
- @Test
- public void testPrepareClearBoundsForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- assertEquals(wct.getChanges().size(), 2);
- Change boundsChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(boundsChange1);
- assertNotEquals(
- (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
-
- Change boundsChange2 = wct.getChanges().get(token2.binder());
- assertNotNull(boundsChange2);
- assertNotEquals(
- (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
- }
-
- @Test
- public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- // Only clear bounds for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only task with freeform windowing mode and the right display should be updated
- assertEquals(wct.getChanges().size(), 1);
- Change wmModeChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(wmModeChange1);
- assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only clear freeform for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
- RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
- taskInfo.displayId = 1;
- taskInfo.token = token.token();
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- return taskInfo;
- }
-
- private static class MockToken {
- private final WindowContainerToken mToken;
- private final IBinder mBinder;
-
- MockToken() {
- mToken = mock(WindowContainerToken.class);
- mBinder = mock(IBinder.class);
- when(mToken.asBinder()).thenReturn(mBinder);
- }
-
- WindowContainerToken token() {
- return mToken;
- }
-
- IBinder binder() {
- return mBinder;
- }
- }
}
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 7896247..d75c36c 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
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -37,6 +38,7 @@
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
@@ -55,6 +57,7 @@
import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -64,7 +67,6 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
-import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Rule;
@@ -94,7 +96,10 @@
private IActivityTaskManager mActivityTaskManager;
@Mock
- private IOnBackInvokedCallback mIOnBackInvokedCallback;
+ private IOnBackInvokedCallback mAppCallback;
+
+ @Mock
+ private IOnBackInvokedCallback mAnimatorCallback;
@Mock
private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@@ -103,14 +108,9 @@
private IRemoteAnimationRunner mBackAnimationRunner;
@Mock
- private Transitions mTransitions;
-
- @Mock
private ShellController mShellController;
private BackAnimationController mController;
-
- private int mEventTime = 0;
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
@@ -127,19 +127,18 @@
mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver, mTransitions);
+ mContentResolver);
mController.setEnableUAnimation(true);
mShellInit.init();
- mEventTime = 0;
mShellExecutor.flushAll();
}
- private void createNavigationInfo(int backType, IOnBackInvokedCallback onBackInvokedCallback) {
+ private void createNavigationInfo(int backType, boolean enableAnimation) {
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
.setType(backType)
.setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
- .setOnBackInvokedCallback(onBackInvokedCallback)
- .setPrepareRemoteAnimation(true);
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(enableAnimation);
createNavigationInfo(builder);
}
@@ -180,26 +179,39 @@
}
@Test
- public void verifyAnimationFinishes() {
- RemoteAnimationTarget animationTarget = createAnimationTarget();
- boolean[] backNavigationDone = new boolean[]{false};
- boolean[] triggerBack = new boolean[]{false};
- createNavigationInfo(new BackNavigationInfo.Builder()
- .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY)
- .setOnBackNavigationDone(
- new RemoteCallback(result -> {
- backNavigationDone[0] = true;
- triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
- })));
- triggerBackGesture();
- assertTrue("Navigation Done callback not called", backNavigationDone[0]);
- assertTrue("TriggerBack should have been true", triggerBack[0]);
+ public void verifyNavigationFinishes() throws RemoteException {
+ final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE,
+ BackNavigationInfo.TYPE_CALLBACK };
+
+ for (int type: testTypes) {
+ registerAnimation(type);
+ }
+
+ for (int type: testTypes) {
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(true)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationFinished();
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+ }
}
@Test
public void backToHome_dispatchesEvents() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, mIOnBackInvokedCallback);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
@@ -207,14 +219,16 @@
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+
+ verify(mAnimatorCallback).onBackStarted(any(BackEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
+ ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verify(mIOnBackInvokedCallback).onBackInvoked();
+ verify(mAnimatorCallback).onBackInvoked();
}
@Test
@@ -225,99 +239,96 @@
mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver, mTransitions);
+ mContentResolver);
shellInit.init();
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
triggerBackGesture();
- verify(appCallback, never()).onBackStarted(any(BackEvent.class));
- verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(appCallback, times(1)).onBackInvoked();
+ verify(mAppCallback, never()).onBackStarted(any());
+ verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture());
+ verify(mAppCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
- verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
+ verify(mAnimatorCallback, never()).onBackInvoked();
verify(mBackAnimationRunner, never()).onAnimationStart(
anyInt(), any(), any(), any(), any());
}
@Test
public void ignoresGesture_transitionInProgress() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
// Check that back invocation is dispatched.
- verify(mIOnBackInvokedCallback).onBackInvoked();
+ verify(mAnimatorCallback).onBackInvoked();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
- reset(mIOnBackInvokedCallback);
+ reset(mAnimatorCallback);
reset(mBackAnimationRunner);
// Verify that we prevent animation from restarting if another gestures happens before
// the previous transition is finished.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- verifyNoMoreInteractions(mIOnBackInvokedCallback);
- mController.onBackAnimationFinished();
- // Pretend the transition handler called finishAnimation.
- mController.finishBackNavigation();
+ verifyNoMoreInteractions(mAnimatorCallback);
+
+ // Finish back navigation.
+ simulateRemoteAnimationFinished();
// Verify that more events from a rejected swipe cannot start animation.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verifyNoMoreInteractions(mIOnBackInvokedCallback);
+ verifyNoMoreInteractions(mAnimatorCallback);
// Verify that we start accepting gestures again once transition finishes.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
}
@Test
public void acceptsGesture_transitionTimeout() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+
+ // In case it is still running in animation.
+ doNothing().when(mAnimatorCallback).onBackInvoked();
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- reset(mIOnBackInvokedCallback);
-
// Simulate transition timeout.
mShellExecutor.flushAll();
- mController.onBackAnimationFinished();
- // Pretend the transition handler called finishAnimation.
- mController.finishBackNavigation();
+ reset(mAnimatorCallback);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any());
}
-
@Test
public void cancelBackInvokeWhenLostFocus() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
// Check that back invocation is dispatched.
@@ -327,11 +338,70 @@
IBinder token = mock(IBinder.class);
mController.mFocusObserver.focusLost(token);
mShellExecutor.flushAll();
- verify(mIOnBackInvokedCallback).onBackCancelled();
+ verify(mAnimatorCallback).onBackCancelled();
// No more back invoke.
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ }
+
+ @Test
+ public void animationNotDefined() throws RemoteException {
+ final int[] testTypes = new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE};
+
+ for (int type: testTypes) {
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(true)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ simulateRemoteAnimationStart(type);
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+ }
+
+ verify(mAppCallback, never()).onBackStarted(any());
+ verify(mAppCallback, never()).onBackProgressed(any());
+ verify(mAppCallback, times(testTypes.length)).onBackInvoked();
+
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(any());
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ }
+
+ @Test
+ public void callbackShouldDeliverProgress() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+ final int type = BackNavigationInfo.TYPE_CALLBACK;
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+
+ verify(mAppCallback, times(1)).onBackStarted(any());
+ verify(mAppCallback, times(1)).onBackProgressed(any());
+ verify(mAppCallback, times(1)).onBackInvoked();
+
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(any());
+ verify(mAnimatorCallback, never()).onBackInvoked();
}
private void doMotionEvent(int actionDown, int coordinate) {
@@ -339,7 +409,6 @@
coordinate, coordinate,
actionDown,
BackEvent.EDGE_LEFT);
- mEventTime += 10;
}
private void simulateRemoteAnimationStart(int type) throws RemoteException {
@@ -351,4 +420,24 @@
mShellExecutor.flushAll();
}
}
+
+ private void simulateRemoteAnimationFinished() {
+ mController.onBackAnimationFinished();
+ mController.finishBackNavigation();
+ }
+
+ private void registerAnimation(int type) {
+ mController.registerAnimation(type,
+ new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ }
+
+ private static class ResultListener implements RemoteCallback.OnResultListener {
+ boolean mBackNavigationDone = false;
+ boolean mTriggerBack = false;
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ mBackNavigationDone = true;
+ mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
+ }
+ };
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 0000000..1e0f9bc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index a6f19e7..40f2e88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -97,13 +97,13 @@
@Test
public void showInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.showInsets(ime(), true);
+ mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */);
verifyZeroInteractions(mExecutor);
}
@Test
public void hideInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.hideInsets(ime(), true);
+ mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */);
verifyZeroInteractions(mExecutor);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 39db328..956f1cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.RemoteException;
import android.util.SparseArray;
@@ -33,6 +34,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
@@ -111,8 +113,10 @@
WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
+ null /* statsToken */);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
+ null /* statsToken */);
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -131,8 +135,10 @@
WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
+ null /* statsToken */);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
+ null /* statsToken */);
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -191,12 +197,12 @@
}
@Override
- public void showInsets(int types, boolean fromIme) {
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
showInsetsCount++;
}
@Override
- public void hideInsets(int types, boolean fromIme) {
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
hideInsetsCount++;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 5332476..3d77948 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -105,7 +105,8 @@
@Test
public void testUpdateDivideBounds() {
mSplitLayout.updateDivideBounds(anyInt());
- verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class));
+ verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
+ anyInt());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 79b520c..89bafcb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -16,10 +16,13 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -30,13 +33,14 @@
import static org.mockito.ArgumentMatchers.any;
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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -68,6 +72,9 @@
import org.mockito.Mock;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DesktopModeControllerTest extends ShellTestCase {
@@ -83,9 +90,7 @@
@Mock
private Handler mMockHandler;
@Mock
- private Transitions mMockTransitions;
- private TestShellExecutor mExecutor;
-
+ private Transitions mTransitions;
private DesktopModeController mController;
private DesktopModeTaskRepository mDesktopModeTaskRepository;
private ShellInit mShellInit;
@@ -97,20 +102,19 @@
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
- mExecutor = new TestShellExecutor();
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
mController = new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
- mDesktopModeTaskRepository, mMockHandler, mExecutor);
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+ mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
- new WindowContainerTransaction());
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
mShellInit.init();
clearInvocations(mShellTaskOrganizer);
clearInvocations(mRootTaskDisplayAreaOrganizer);
+ clearInvocations(mTransitions);
}
@After
@@ -124,113 +128,133 @@
}
@Test
- public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWct = new WindowContainerTransaction();
- MockToken taskMockToken = new MockToken();
- taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWct);
+ public void testDesktopModeEnabled_rootTdaSetToFreeform() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
-
- // The test
mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
- // WCT should have 2 changes - clear task wm mode and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(2);
-
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
- assertThat(taskWmModeChange).isNotNull();
- assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
- // Verify executed WCT has a change for setting display windowing mode to freeform
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to freeform
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
}
@Test
- public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
- MockToken taskWmMockToken = new MockToken();
- taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWmWct);
+ public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // Create a fake WCT to simulate clearing task bounds
- WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
- MockToken taskBoundsMockToken = new MockToken();
- taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
- when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskBoundsWct);
-
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
-
- // The test
mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to fullscreen
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
- // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(3);
+ @Test
+ public void testDesktopModeEnabled_windowingModeCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
- assertThat(taskWmMode).isNotNull();
- assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- // Verify executed WCT has a change for clearing task bounds
- Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
- assertThat(bounds).isNotNull();
- assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
- assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+ // 2 changes: Root TDA windowing mode and 1 task
+ assertThat(wct.getChanges().size()).isEqualTo(2);
+ // No changes for tasks that are not standard or freeform
+ assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard freeform task has windowing mode cleared
+ Change change = wct.getChanges().get(freeformTask.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ }
- // Verify executed WCT has a change for setting display windowing mode to fullscreen
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ @Test
+ public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
+
+ mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 3 changes: Root TDA windowing mode and 2 tasks
+ assertThat(wct.getChanges().size()).isEqualTo(3);
+ // No changes to home task
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard tasks have bounds cleared
+ assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
+ assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
+ // Freeform standard tasks have windowing mode cleared
+ assertThat(wct.getChanges().get(
+ freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
+ WINDOWING_MODE_UNDEFINED);
+ }
+
+ @Test
+ public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
+ createMockDisplayArea();
+ RunningTaskInfo fullscreenTask1 = createFullscreenTask();
+ fullscreenTask1.isVisible = true;
+ RunningTaskInfo fullscreenTask2 = createFullscreenTask();
+ fullscreenTask2.isVisible = false;
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
+
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // Check that there are hierarchy changes for home task and visible task
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // First show home task
+ WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
+
+ // Then visible task on top of it
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
}
@Test
public void testShowDesktopApps() {
// Set up two active tasks on desktop
- mDesktopModeTaskRepository.addActiveTask(1);
- mDesktopModeTaskRepository.addActiveTask(2);
- MockToken token1 = new MockToken();
- MockToken token2 = new MockToken();
- ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
- token1.token()).setLastActiveTime(100).build();
- ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
- token2.token()).setLastActiveTime(200).build();
- when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
- when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+ RunningTaskInfo freeformTask1 = createFreeformTask();
+ freeformTask1.lastActiveTime = 100;
+ RunningTaskInfo freeformTask2 = createFreeformTask();
+ freeformTask2.lastActiveTime = 200;
+ mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
+ freeformTask1);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
+ freeformTask2);
// Run show desktop apps logic
mController.showDesktopApps();
ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
- verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ }
WindowContainerTransaction wct = wctCaptor.getValue();
// Check wct has reorder calls
@@ -239,12 +263,12 @@
// Task 2 has activity later, must be first
WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(token2.binder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
// Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(token2.binder());
+ assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
}
@Test
@@ -266,7 +290,7 @@
@Test
public void testHandleTransitionRequest_notFreeform_returnsNull() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
WindowContainerTransaction wct = mController.handleRequest(
new Binder(),
@@ -276,7 +300,7 @@
@Test
public void testHandleTransitionRequest_returnsWct() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.token = new MockToken().mToken;
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
WindowContainerTransaction wct = mController.handleRequest(
@@ -285,6 +309,57 @@
assertThat(wct).isNotNull();
}
+ private DisplayAreaInfo createMockDisplayArea() {
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
+ mContext.getDisplayId(), 0);
+ when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+ .thenReturn(displayAreaInfo);
+ return displayAreaInfo;
+ }
+
+ private RunningTaskInfo createFreeformTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createFullscreenTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createHomeTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private WindowContainerTransaction getDesktopModeSwitchTransaction() {
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
+ } else {
+ verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ }
+ return arg.getValue();
+ }
+
+ private void assertThatBoundsCleared(Change change) {
+ assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
+ assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+ }
+
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;
@@ -298,9 +373,5 @@
WindowContainerToken token() {
return mToken;
}
-
- IBinder binder() {
- return mBinder;
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
index 11948dbf..f3f7067 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -24,7 +24,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -42,13 +41,11 @@
private BackgroundWindowManager mBackgroundWindowManager;
@Mock
private DisplayLayout mMockDisplayLayout;
- @Mock
- private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer);
+ mBackgroundWindowManager = new BackgroundWindowManager(mContext);
mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index d01f3d3..38b75f8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -16,18 +16,24 @@
package com.android.wm.shell.splitscreen;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
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.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -35,6 +41,8 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -65,11 +73,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for {@link SplitScreenController}
*/
@@ -91,18 +99,21 @@
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
- @Mock Optional<RecentTasksController> mRecentTasks;
+ @Mock StageCoordinator mStageCoordinator;
+ @Mock RecentTasksController mRecentTasks;
+ @Captor ArgumentCaptor<Intent> mIntentCaptor;
private SplitScreenController mSplitScreenController;
@Before
public void setup() {
+ assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor));
+ mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
}
@Test
@@ -148,58 +159,100 @@
}
@Test
- public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
- doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
- doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
-
- // Verify launching the same activity returns true.
+ public void testStartIntent_appendsNoUserActionFlag() {
Intent startIntent = createStartIntent("startActivity");
- ActivityManager.RunningTaskInfo focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Verify launching different activity returns false.
- Intent diffIntent = createStartIntent("diffActivity");
- focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@Test
- public void testShouldAddMultipleTaskFlag_inSplitScreen() {
- doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+ // Put the same component into a task in the background
+ ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- Intent diffIntent = createStartIntent("diffActivity");
- ActivityManager.RunningTaskInfo differentTaskInfo =
- createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ // Put the same component into a task in the background
+ doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+ .findTaskInBackground(any());
- // Verify launching the same activity return false.
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
- // Verify launching the same activity as adjacent returns true.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
- // Verify launching different activity from adjacent returns false.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ @Test
+ public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+ doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ ActivityManager.RunningTaskInfo sameTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).switchSplitPosition(anyString());
}
private Intent createStartIntent(String activityName) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
new file mode 100644
index 0000000..ac10ddb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -0,0 +1,130 @@
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [TaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:TaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskPositionerTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock
+ private lateinit var mockWindowDecoration: WindowDecoration<*>
+ @Mock
+ private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+
+ @Mock
+ private lateinit var taskToken: WindowContainerToken
+ @Mock
+ private lateinit var taskBinder: IBinder
+
+ private lateinit var taskPositioner: TaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ taskPositioner = TaskPositioner(
+ mockShellTaskOrganizer,
+ mockWindowDecoration,
+ mockDragStartListener
+ )
+ `when`(taskToken.asBinder()).thenReturn(taskBinder)
+ mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ }
+ }
+
+ @Test
+ fun testDragResize_move_skipsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_UNDEFINED, // Move
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Move the task 10px to the right.
+ val newX = STARTING_BOUNDS.left.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize the task by 10px to the right.
+ val newX = STARTING_BOUNDS.right.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ !change.dragResizing
+ }
+ })
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ }
+}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index eb8d26a..b1f327c 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -211,6 +211,8 @@
"tests/data/**/*.apk",
"tests/data/**/*.arsc",
"tests/data/**/*.idmap",
+ ":FrameworkResourcesSparseTestApp",
+ ":FrameworkResourcesNotSparseTestApp",
],
test_suites: ["device-tests"],
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 1381bdd..06ffb72 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -43,28 +43,19 @@
using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
+/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
+ * and so access to ->value() and ->map_entry() are safe here
+ */
base::expected<EntryValue, IOError> GetEntryValue(
incfs::verified_map_ptr<ResTable_entry> table_entry) {
- const uint16_t entry_size = dtohs(table_entry->size);
+ const uint16_t entry_size = table_entry->size();
// Check if the entry represents a bag value.
- if (entry_size >= sizeof(ResTable_map_entry) &&
- (dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
- const auto map_entry = table_entry.convert<ResTable_map_entry>();
- if (!map_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- return map_entry.verified();
+ if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) {
+ return table_entry.convert<ResTable_map_entry>().verified();
}
- // The entry represents a non-bag value.
- const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
- if (!entry_value) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- Res_value value;
- value.copyFrom_dtoh(entry_value.value());
- return value;
+ return table_entry->value();
}
} // namespace
@@ -814,17 +805,12 @@
return base::unexpected(std::nullopt);
}
- auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
- if (!best_entry_result.has_value()) {
- return base::unexpected(best_entry_result.error());
+ auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+ if (!best_entry_verified.has_value()) {
+ return base::unexpected(best_entry_verified.error());
}
- const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
- if (!best_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
-
- const auto entry = GetEntryValue(best_entry.verified());
+ const auto entry = GetEntryValue(*best_entry_verified);
if (!entry.has_value()) {
return base::unexpected(entry.error());
}
@@ -837,7 +823,7 @@
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
- best_entry->key.index),
+ (*best_entry_verified)->key()),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 5b69cca..386f718 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -88,7 +88,9 @@
// Make sure that there is enough room for the entry offsets.
const size_t offsets_offset = dtohs(header->header.headerSize);
const size_t entries_offset = dtohl(header->entriesStart);
- const size_t offsets_length = sizeof(uint32_t) * entry_count;
+ const size_t offsets_length = header->flags & ResTable_type::FLAG_OFFSET16
+ ? sizeof(uint16_t) * entry_count
+ : sizeof(uint32_t) * entry_count;
if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.";
@@ -107,8 +109,8 @@
return true;
}
-static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
- incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
+static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (UNLIKELY(entry_offset & 0x03U)) {
LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
@@ -136,7 +138,7 @@
return base::unexpected(IOError::PAGES_MISSING);
}
- const size_t entry_size = dtohs(entry->size);
+ const size_t entry_size = entry->size();
if (UNLIKELY(entry_size < sizeof(entry.value()))) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
@@ -149,6 +151,11 @@
return base::unexpected(std::nullopt);
}
+ // If entry is compact, value is already encoded, and a compact entry
+ // cannot be a map_entry, we are done verifying
+ if (entry->is_compact())
+ return entry.verified();
+
if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) {
@@ -192,7 +199,7 @@
return base::unexpected(std::nullopt);
}
}
- return {};
+ return entry.verified();
}
LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei)
@@ -228,7 +235,7 @@
entryIndex_);
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) {
base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index);
if (UNLIKELY(!entry_offset.has_value())) {
@@ -242,14 +249,13 @@
// The configuration matches and is better than the previous selection.
// Find the entry value if it exists for this configuration.
const size_t entry_count = dtohl(type_chunk->entryCount);
- const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+ const auto offsets = type_chunk.offset(dtohs(type_chunk->header.headerSize));
// Check if there is the desired entry in this type.
if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
// This is encoded as a sparse map, so perform a binary search.
bool error = false;
- auto sparse_indices = type_chunk.offset(offsets_offset)
- .convert<ResTable_sparseTypeEntry>().iterator();
+ auto sparse_indices = offsets.convert<ResTable_sparseTypeEntry>().iterator();
auto sparse_indices_end = sparse_indices + entry_count;
auto result = std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
[&error](const incfs::map_ptr<ResTable_sparseTypeEntry>& entry,
@@ -284,26 +290,36 @@
return base::unexpected(std::nullopt);
}
- const auto entry_offset_ptr = type_chunk.offset(offsets_offset).convert<uint32_t>() + entry_index;
- if (UNLIKELY(!entry_offset_ptr)) {
- return base::unexpected(IOError::PAGES_MISSING);
+ uint32_t result;
+
+ if (type_chunk->flags & ResTable_type::FLAG_OFFSET16) {
+ const auto entry_offset_ptr = offsets.convert<uint16_t>() + entry_index;
+ if (UNLIKELY(!entry_offset_ptr)) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ result = offset_from16(entry_offset_ptr.value());
+ } else {
+ const auto entry_offset_ptr = offsets.convert<uint32_t>() + entry_index;
+ if (UNLIKELY(!entry_offset_ptr)) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ result = dtohl(entry_offset_ptr.value());
}
- const uint32_t value = dtohl(entry_offset_ptr.value());
- if (value == ResTable_type::NO_ENTRY) {
+ if (result == ResTable_type::NO_ENTRY) {
return base::unexpected(std::nullopt);
}
-
- return value;
+ return result;
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) {
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk,
+ uint32_t offset) {
auto valid = VerifyResTableEntry(type_chunk, offset);
if (UNLIKELY(!valid.has_value())) {
return base::unexpected(valid.error());
}
- return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>();
+ return valid;
}
base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
@@ -376,31 +392,42 @@
for (const auto& type_entry : type_spec->type_entries) {
const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type;
- size_t entry_count = dtohl(type->entryCount);
- for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
- auto entry_offset_ptr = type.offset(dtohs(type->header.headerSize)).convert<uint32_t>() +
- entry_idx;
- if (!entry_offset_ptr) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
+ const size_t entry_count = dtohl(type->entryCount);
+ const auto entry_offsets = type.offset(dtohs(type->header.headerSize));
+ for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
uint32_t offset;
uint16_t res_idx;
if (type->flags & ResTable_type::FLAG_SPARSE) {
- auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>();
+ auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx;
+ if (!sparse_entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
offset = dtohs(sparse_entry->offset) * 4u;
res_idx = dtohs(sparse_entry->idx);
+ } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ auto entry = entry_offsets.convert<uint16_t>() + entry_idx;
+ if (!entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ offset = offset_from16(entry.value());
+ res_idx = entry_idx;
} else {
- offset = dtohl(entry_offset_ptr.value());
+ auto entry = entry_offsets.convert<uint32_t>() + entry_idx;
+ if (!entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ offset = dtohl(entry.value());
res_idx = entry_idx;
}
+
if (offset != ResTable_type::NO_ENTRY) {
auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>();
if (!entry) {
return base::unexpected(IOError::PAGES_MISSING);
}
- if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
+ if (entry->key() == static_cast<uint32_t>(*key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index b3fb145..b68143d 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -143,6 +143,7 @@
{0xACE00000u, 46u}, // ahl -> Latn
{0xB8E00000u, 1u}, // aho -> Ahom
{0x99200000u, 46u}, // ajg -> Latn
+ {0xCD200000u, 2u}, // ajt -> Arab
{0x616B0000u, 46u}, // ak -> Latn
{0xA9400000u, 101u}, // akk -> Xsux
{0x81600000u, 46u}, // ala -> Latn
@@ -1053,6 +1054,7 @@
{0xB70D0000u, 46u}, // nyn -> Latn
{0xA32D0000u, 46u}, // nzi -> Latn
{0x6F630000u, 46u}, // oc -> Latn
+ {0x6F634553u, 46u}, // oc-ES -> Latn
{0x88CE0000u, 46u}, // ogc -> Latn
{0x6F6A0000u, 11u}, // oj -> Cans
{0xC92E0000u, 11u}, // ojs -> Cans
@@ -1093,6 +1095,7 @@
{0xB4EF0000u, 71u}, // phn -> Phnx
{0xAD0F0000u, 46u}, // pil -> Latn
{0xBD0F0000u, 46u}, // pip -> Latn
+ {0xC90F0000u, 46u}, // pis -> Latn
{0x814F0000u, 9u}, // pka -> Brah
{0xB94F0000u, 46u}, // pko -> Latn
{0x706C0000u, 46u}, // pl -> Latn
@@ -1204,12 +1207,14 @@
{0xE1720000u, 46u}, // sly -> Latn
{0x736D0000u, 46u}, // sm -> Latn
{0x81920000u, 46u}, // sma -> Latn
+ {0x8D920000u, 46u}, // smd -> Latn
{0xA5920000u, 46u}, // smj -> Latn
{0xB5920000u, 46u}, // smn -> Latn
{0xBD920000u, 76u}, // smp -> Samr
{0xC1920000u, 46u}, // smq -> Latn
{0xC9920000u, 46u}, // sms -> Latn
{0x736E0000u, 46u}, // sn -> Latn
+ {0x85B20000u, 46u}, // snb -> Latn
{0x89B20000u, 46u}, // snc -> Latn
{0xA9B20000u, 46u}, // snk -> Latn
{0xBDB20000u, 46u}, // snp -> Latn
@@ -1314,6 +1319,7 @@
{0x746F0000u, 46u}, // to -> Latn
{0x95D30000u, 46u}, // tof -> Latn
{0x99D30000u, 46u}, // tog -> Latn
+ {0xA9D30000u, 46u}, // tok -> Latn
{0xC1D30000u, 46u}, // toq -> Latn
{0xA1F30000u, 46u}, // tpi -> Latn
{0xB1F30000u, 46u}, // tpm -> Latn
@@ -1527,6 +1533,7 @@
0x61665A414C61746ELLU, // af_Latn_ZA
0xC0C0434D4C61746ELLU, // agq_Latn_CM
0xB8E0494E41686F6DLLU, // aho_Ahom_IN
+ 0xCD20544E41726162LLU, // ajt_Arab_TN
0x616B47484C61746ELLU, // ak_Latn_GH
0xA940495158737578LLU, // akk_Xsux_IQ
0xB560584B4C61746ELLU, // aln_Latn_XK
@@ -1534,6 +1541,7 @@
0x616D455445746869LLU, // am_Ethi_ET
0xB9804E474C61746ELLU, // amo_Latn_NG
0x616E45534C61746ELLU, // an_Latn_ES
+ 0xB5A04E474C61746ELLU, // ann_Latn_NG
0xE5C049444C61746ELLU, // aoz_Latn_ID
0x8DE0544741726162LLU, // apd_Arab_TG
0x6172454741726162LLU, // ar_Arab_EG
@@ -2039,6 +2047,7 @@
0xB88F49525870656FLLU, // peo_Xpeo_IR
0xACAF44454C61746ELLU, // pfl_Latn_DE
0xB4EF4C4250686E78LLU, // phn_Phnx_LB
+ 0xC90F53424C61746ELLU, // pis_Latn_SB
0x814F494E42726168LLU, // pka_Brah_IN
0xB94F4B454C61746ELLU, // pko_Latn_KE
0x706C504C4C61746ELLU, // pl_Latn_PL
@@ -2119,11 +2128,13 @@
0xE17249444C61746ELLU, // sly_Latn_ID
0x736D57534C61746ELLU, // sm_Latn_WS
0x819253454C61746ELLU, // sma_Latn_SE
+ 0x8D92414F4C61746ELLU, // smd_Latn_AO
0xA59253454C61746ELLU, // smj_Latn_SE
0xB59246494C61746ELLU, // smn_Latn_FI
0xBD92494C53616D72LLU, // smp_Samr_IL
0xC99246494C61746ELLU, // sms_Latn_FI
0x736E5A574C61746ELLU, // sn_Latn_ZW
+ 0x85B24D594C61746ELLU, // snb_Latn_MY
0xA9B24D4C4C61746ELLU, // snk_Latn_ML
0x736F534F4C61746ELLU, // so_Latn_SO
0x99D2555A536F6764LLU, // sog_Sogd_UZ
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5e8a623..267190a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4487,20 +4487,14 @@
return err;
}
- if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (entry.entry->map_entry()) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}
- const Res_value* value = reinterpret_cast<const Res_value*>(
- reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
-
- outValue->size = dtohs(value->size);
- outValue->res0 = value->res0;
- outValue->dataType = value->dataType;
- outValue->data = dtohl(value->data);
+ *outValue = entry.entry->value();
// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
@@ -4691,11 +4685,10 @@
return err;
}
- const uint16_t entrySize = dtohs(entry.entry->size);
- const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
- const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+ const uint16_t entrySize = entry.entry->size();
+ const ResTable_map_entry* map_entry = entry.entry->map_entry();
+ const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0;
+ const uint32_t count = map_entry ? dtohl(map_entry->count) : 0;
size_t N = count;
@@ -4759,7 +4752,7 @@
// Now merge in the new attributes...
size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
- + dtohs(entry.entry->size);
+ + entrySize;
const ResTable_map* map;
bag_entry* entries = (bag_entry*)(set+1);
size_t curEntry = 0;
@@ -5137,7 +5130,7 @@
continue;
}
- if (dtohl(entry->key.index) == (size_t) *ei) {
+ if (entry->key() == (size_t) *ei) {
uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
if (outTypeSpecFlags) {
Entry result;
@@ -6600,8 +6593,12 @@
// Entry does not exist.
continue;
}
-
- thisOffset = dtohl(eindex[realEntryIndex]);
+ if (thisType->flags & ResTable_type::FLAG_OFFSET16) {
+ auto eindex16 = reinterpret_cast<const uint16_t*>(eindex);
+ thisOffset = offset_from16(eindex16[realEntryIndex]);
+ } else {
+ thisOffset = dtohl(eindex[realEntryIndex]);
+ }
}
if (thisOffset == ResTable_type::NO_ENTRY) {
@@ -6651,8 +6648,8 @@
const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
- if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+ if (entry->size() < sizeof(*entry)) {
+ ALOGW("ResTable_entry size 0x%zx is too small", entry->size());
return BAD_TYPE;
}
@@ -6663,7 +6660,7 @@
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
- outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
+ outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key());
}
return NO_ERROR;
}
@@ -7653,6 +7650,9 @@
if (type->flags & ResTable_type::FLAG_SPARSE) {
printf(" [sparse]");
}
+ if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ printf(" [offset16]");
+ }
}
printf(":\n");
@@ -7684,7 +7684,13 @@
thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u;
} else {
entryId = entryIndex;
- thisOffset = dtohl(eindex[entryIndex]);
+ if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ const auto eindex16 =
+ reinterpret_cast<const uint16_t*>(eindex);
+ thisOffset = offset_from16(eindex16[entryIndex]);
+ } else {
+ thisOffset = dtohl(eindex[entryIndex]);
+ }
if (thisOffset == ResTable_type::NO_ENTRY) {
continue;
}
@@ -7734,7 +7740,7 @@
continue;
}
- uintptr_t esize = dtohs(ent->size);
+ uintptr_t esize = ent->size();
if ((esize&0x3) != 0) {
printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
continue;
@@ -7746,30 +7752,27 @@
}
const Res_value* valuePtr = NULL;
- const ResTable_map_entry* bagPtr = NULL;
+ const ResTable_map_entry* bagPtr = ent->map_entry();
Res_value value;
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (bagPtr) {
printf("<bag>");
- bagPtr = (const ResTable_map_entry*)ent;
} else {
- valuePtr = (const Res_value*)
- (((const uint8_t*)ent) + esize);
- value.copyFrom_dtoh(*valuePtr);
+ value = ent->value();
printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
(int)value.dataType, (int)value.data,
(int)value.size, (int)value.res0);
}
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ if (ent->flags() & ResTable_entry::FLAG_PUBLIC) {
printf(" (PUBLIC)");
}
printf("\n");
if (inclValues) {
- if (valuePtr != NULL) {
+ if (bagPtr == NULL) {
printf(" ");
print_value(typeConfigs->package, value);
- } else if (bagPtr != NULL) {
+ } else {
const int N = dtohl(bagPtr->count);
const uint8_t* baseMapPtr = (const uint8_t*)ent;
size_t mapOffset = esize;
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 647aa19..70d14a1 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -59,7 +59,9 @@
+ dtohl(type->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
- if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) {
+ const size_t indexSize = type->flags & ResTable_type::FLAG_OFFSET16 ?
+ sizeof(uint16_t) : sizeof(uint32_t);
+ if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) {
ALOGE("Type's entry indices extend beyond its boundaries");
return NULL;
}
@@ -73,6 +75,9 @@
}
entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u;
+ } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices);
+ entryOffset = offset_from16(entryIndices16[mIndex]);
} else {
entryOffset = dtohl(entryIndices[mIndex]);
}
@@ -91,11 +96,11 @@
if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
return NULL;
- } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+ } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
return NULL;
- } else if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+ } else if (entry->size() < sizeof(*entry)) {
+ ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
return NULL;
}
return entry;
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
index 58fc5bb..a1385f2 100644
--- a/libs/androidfw/ZipUtils.cpp
+++ b/libs/androidfw/ZipUtils.cpp
@@ -35,7 +35,7 @@
using namespace android;
// TODO: This can go away once the only remaining usage in aapt goes away.
-class FileReader : public zip_archive::Reader {
+class FileReader final : public zip_archive::Reader {
public:
explicit FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) {
}
@@ -66,7 +66,7 @@
mutable off64_t mCurrentOffset;
};
-class FdReader : public zip_archive::Reader {
+class FdReader final : public zip_archive::Reader {
public:
explicit FdReader(int fd) : mFd(fd) {
}
@@ -79,7 +79,7 @@
const int mFd;
};
-class BufferReader : public zip_archive::Reader {
+class BufferReader final : public zip_archive::Reader {
public:
BufferReader(incfs::map_ptr<void> input, size_t inputSize) : Reader(),
mInput(input.convert<uint8_t>()),
@@ -105,7 +105,7 @@
const size_t mInputSize;
};
-class BufferWriter : public zip_archive::Writer {
+class BufferWriter final : public zip_archive::Writer {
public:
BufferWriter(void* output, size_t outputSize) : Writer(),
mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) {
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index e459639..79d96282 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -166,14 +166,14 @@
base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
const std::u16string& entry_name) const;
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
// Returns the string pool where type names are stored.
const ResStringPool* GetTypeStringPool() const {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 9309091..c740832 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -26,6 +26,7 @@
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
+#include <utils/ByteOrder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -53,7 +54,7 @@
// The version should only be changed when a backwards-incompatible change must be made to the
// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
// to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
// Returns whether or not the path represents a fabricated overlay.
bool IsFabricatedOverlay(const std::string& path);
@@ -1437,6 +1438,10 @@
// Mark any types that use this with a v26 qualifier to prevent runtime issues on older
// platforms.
FLAG_SPARSE = 0x01,
+
+ // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u
+ // An 16-bit offset of 0xffffu means a NO_ENTRY
+ FLAG_OFFSET16 = 0x02,
};
uint8_t flags;
@@ -1453,6 +1458,11 @@
ResTable_config config;
};
+// Convert a 16-bit offset to 32-bit if FLAG_OFFSET16 is set
+static inline uint32_t offset_from16(uint16_t off16) {
+ return dtohs(off16) == 0xffffu ? ResTable_type::NO_ENTRY : dtohs(off16) * 4u;
+}
+
// The minimum size required to read any version of ResTable_type.
constexpr size_t kResTableTypeMinSize =
sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size);
@@ -1480,6 +1490,8 @@
static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
"ResTable_sparseTypeEntry must be 4 bytes in size");
+struct ResTable_map_entry;
+
/**
* This is the beginning of information about an entry in the resource
* table. It holds the reference to the name of this entry, and is
@@ -1487,12 +1499,11 @@
* * A Res_value structure, if FLAG_COMPLEX is -not- set.
* * An array of ResTable_map structures, if FLAG_COMPLEX is set.
* These supply a set of name/value mappings of data.
+ * * If FLAG_COMPACT is set, this entry is a compact entry for
+ * simple values only
*/
-struct ResTable_entry
+union ResTable_entry
{
- // Number of bytes in this structure.
- uint16_t size;
-
enum {
// If set, this is a complex entry, holding a set of name/value
// mappings. It is followed by an array of ResTable_map structures.
@@ -1504,18 +1515,91 @@
// resources of the same name/type. This is only useful during
// linking with other resource tables.
FLAG_WEAK = 0x0004,
+ // If set, this is a compact entry with data type and value directly
+ // encoded in the this entry, see ResTable_entry::compact
+ FLAG_COMPACT = 0x0008,
};
- uint16_t flags;
-
- // Reference into ResTable_package::keyStrings identifying this entry.
- struct ResStringPool_ref key;
+
+ struct Full {
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ uint16_t flags;
+
+ // Reference into ResTable_package::keyStrings identifying this entry.
+ struct ResStringPool_ref key;
+ } full;
+
+ /* A compact entry is indicated by FLAG_COMPACT, with flags at the same
+ * offset as a normal entry. This is only for simple data values where
+ *
+ * - size for entry or value can be inferred (both being 8 bytes).
+ * - key index is encoded in 16-bit
+ * - dataType is encoded as the higher 8-bit of flags
+ * - data is encoded directly in this entry
+ */
+ struct Compact {
+ uint16_t key;
+ uint16_t flags;
+ uint32_t data;
+ } compact;
+
+ uint16_t flags() const { return dtohs(full.flags); };
+ bool is_compact() const { return flags() & FLAG_COMPACT; }
+ bool is_complex() const { return flags() & FLAG_COMPLEX; }
+
+ size_t size() const {
+ return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size);
+ }
+
+ uint32_t key() const {
+ return is_compact() ? dtohs(this->compact.key) : dtohl(this->full.key.index);
+ }
+
+ /* Always verify the memory associated with this entry and its value
+ * before calling value() or map_entry()
+ */
+ Res_value value() const {
+ Res_value v;
+ if (is_compact()) {
+ v.size = sizeof(Res_value);
+ v.res0 = 0;
+ v.data = dtohl(this->compact.data);
+ v.dataType = dtohs(compact.flags) >> 8;
+ } else {
+ auto vaddr = reinterpret_cast<const uint8_t*>(this) + dtohs(this->full.size);
+ auto value = reinterpret_cast<const Res_value*>(vaddr);
+ v.size = dtohs(value->size);
+ v.res0 = value->res0;
+ v.data = dtohl(value->data);
+ v.dataType = value->dataType;
+ }
+ return v;
+ }
+
+ const ResTable_map_entry* map_entry() const {
+ return is_complex() && !is_compact() ?
+ reinterpret_cast<const ResTable_map_entry*>(this) : nullptr;
+ }
};
+/* Make sure size of ResTable_entry::Full and ResTable_entry::Compact
+ * be the same as ResTable_entry. This is to allow iteration of entries
+ * to work in either cases.
+ *
+ * The offset of flags must be at the same place for both structures,
+ * to ensure the correct reading to decide whether this is a full entry
+ * or a compact entry.
+ */
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Full));
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Compact));
+static_assert(offsetof(ResTable_entry, full.flags) == offsetof(ResTable_entry, compact.flags));
+
/**
* Extended form of a ResTable_entry for map entries, defining a parent map
* resource from which to inherit values.
*/
-struct ResTable_map_entry : public ResTable_entry
+struct ResTable_map_entry : public ResTable_entry::Full
{
// Resource identifier of the parent mapping, or 0 if there is none.
// This is always treated as a TYPE_DYNAMIC_REFERENCE.
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d214e2d..c90ec19 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -71,62 +71,6 @@
ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
}
-TEST(LoadedArscTest, LoadSparseEntryApp) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
- contents.length());
- ASSERT_THAT(loaded_arsc, NotNull());
-
- const LoadedPackage* package =
- loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
- ASSERT_THAT(package, NotNull());
-
- const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
- const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
-
- const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
- ASSERT_THAT(type_spec, NotNull());
- ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
- auto type = type_spec->type_entries[0];
- ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
-}
-
-TEST(LoadedArscTest, FindSparseEntryApp) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
- contents.length());
- ASSERT_THAT(loaded_arsc, NotNull());
-
- const LoadedPackage* package =
- loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26));
- ASSERT_THAT(package, NotNull());
-
- const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1;
- const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26);
-
- const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
- ASSERT_THAT(type_spec, NotNull());
- ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
- // Ensure that AAPT2 sparsely encoded the v26 config as expected.
- auto type_entry = std::find_if(
- type_spec->type_entries.begin(), type_spec->type_entries.end(),
- [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; });
- ASSERT_NE(type_entry, type_spec->type_entries.end());
- ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0);
-
- // Test fetching a resource with only sparsely encoded configs by name.
- auto id = package->FindEntryByName(u"string", u"only_v26");
- ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0));
-}
-
TEST(LoadedArscTest, LoadSharedLibrary) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
@@ -404,4 +348,84 @@
// sizeof(Res_value) might not be backwards compatible.
// TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
+class LoadedArscParameterizedTest :
+ public testing::TestWithParam<std::string> {
+};
+
+TEST_P(LoadedArscParameterizedTest, LoadSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+ contents.length());
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+ ASSERT_THAT(package, NotNull());
+
+ const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+ const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+ auto type = type_spec->type_entries[0];
+ ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
+}
+
+TEST_P(LoadedArscParameterizedTest, FindSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+ contents.length());
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_land));
+ ASSERT_THAT(package, NotNull());
+
+ const uint8_t type_index = get_type_id(sparse::R::string::only_land) - 1;
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+ // Type Entry with default orientation is not sparse encoded because the ratio of
+ // populated entries to total entries is above threshold.
+ // Only find out default locale because Soong build system will introduce pseudo
+ // locales for the apk generated at runtime.
+ auto type_entry_default = std::find_if(
+ type_spec->type_entries.begin(), type_spec->type_entries.end(),
+ [] (const TypeSpec::TypeEntry& x) { return x.config.orientation == 0 &&
+ x.config.locale == 0; });
+ ASSERT_NE(type_entry_default, type_spec->type_entries.end());
+ ASSERT_EQ(type_entry_default->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+ // Type Entry with land orientation is sparse encoded as expected.
+ // Only find out default locale because Soong build system will introduce pseudo
+ // locales for the apk generated at runtime.
+ auto type_entry_land = std::find_if(
+ type_spec->type_entries.begin(), type_spec->type_entries.end(),
+ [](const TypeSpec::TypeEntry& x) { return x.config.orientation ==
+ ResTable_config::ORIENTATION_LAND &&
+ x.config.locale == 0; });
+ ASSERT_NE(type_entry_land, type_spec->type_entries.end());
+ ASSERT_NE(type_entry_land->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+ // Test fetching a resource with only sparsely encoded configs by name.
+ auto id = package->FindEntryByName(u"string", u"only_land");
+ ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_land, 0));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ FrameWorkResourcesLoadedArscTests,
+ LoadedArscParameterizedTest,
+ ::testing::Values(
+ base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+ base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+ ));
+
} // namespace android
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index 9aeb00c..fbf7098 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -15,6 +15,7 @@
*/
#include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
#include <codecvt>
#include <locale>
@@ -41,34 +42,6 @@
ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
}
-TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- ResTable table;
- ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
-
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- table.setParameters(&config);
-
- String16 name(u"com.android.sparse:integer/foo_9");
- uint32_t flags;
- uint32_t resid =
- table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
- ASSERT_NE(0u, resid);
-
- Res_value val;
- ResTable_config selected_config;
- ASSERT_GE(
- table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
- 0);
- EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
- EXPECT_EQ(900u, val.data);
-}
-
TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
@@ -476,4 +449,43 @@
ASSERT_FALSE(invalid_pool->stringAt(invalid_val.data).has_value());
}
+class ResTableParameterizedTest :
+ public testing::TestWithParam<std::string> {
+};
+
+TEST_P(ResTableParameterizedTest, ShouldLoadSparseEntriesSuccessfully) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.orientation = ResTable_config::ORIENTATION_LAND;
+ table.setParameters(&config);
+
+ String16 name(u"com.android.sparse:integer/foo_9");
+ uint32_t flags;
+ uint32_t resid =
+ table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
+ ASSERT_NE(0u, resid);
+
+ Res_value val;
+ ResTable_config selected_config;
+ ASSERT_GE(
+ table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
+ 0);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ EXPECT_EQ(900u, val.data);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ FrameWorkResourcesResTableTests,
+ ResTableParameterizedTest,
+ ::testing::Values(
+ base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+ base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+ ));
+
} // namespace android
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
index c9b4ad8..fffeeb8 100644
--- a/libs/androidfw/tests/SparseEntry_bench.cpp
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -16,6 +16,7 @@
#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
#include "BenchmarkHelpers.h"
#include "data/sparse/R.h"
@@ -24,40 +25,74 @@
namespace android {
+static void BM_SparseEntryGetResourceHelper(const std::vector<std::string>& paths,
+ uint32_t resid, benchmark::State& state, void (*GetResourceBenchmarkFunc)(
+ const std::vector<std::string>&, const ResTable_config*,
+ uint32_t, benchmark::State&)){
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.orientation = ResTable_config::ORIENTATION_LAND;
+ GetResourceBenchmarkFunc(paths, &config, resid, state);
+}
+
static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+ state, &GetResourceBenchmarkOld);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+ state, &GetResourceBenchmarkOld);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+ state, &GetResourceBenchmark);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+ state, &GetResourceBenchmark);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999);
+static void BM_SparseEntryGetResourceOldSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceOldNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Large, sparse::R::string::foo_999);
+
} // namespace android
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d69abe5..ed30904 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <algorithm>
#include <androidfw/ResourceTypes.h>
#include <androidfw/TypeWrappers.h>
#include <utils/String8.h>
@@ -22,88 +23,123 @@
namespace android {
-void* createTypeData() {
- ResTable_type t;
- memset(&t, 0, sizeof(t));
+// create a ResTable_type in memory with a vector of Res_value*
+static ResTable_type* createTypeTable(std::vector<Res_value*>& values,
+ bool compact_entry = false,
+ bool short_offsets = false)
+{
+ ResTable_type t{};
t.header.type = RES_TABLE_TYPE_TYPE;
t.header.headerSize = sizeof(t);
+ t.header.size = sizeof(t);
t.id = 1;
- t.entryCount = 3;
+ t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0;
- uint32_t offsets[3];
- t.entriesStart = t.header.headerSize + sizeof(offsets);
- t.header.size = t.entriesStart;
+ t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t));
+ t.entriesStart = t.header.size;
+ t.entryCount = values.size();
- offsets[0] = 0;
- ResTable_entry e1;
- memset(&e1, 0, sizeof(e1));
- e1.size = sizeof(e1);
- e1.key.index = 0;
- t.header.size += sizeof(e1);
+ size_t entry_size = compact_entry ? sizeof(ResTable_entry)
+ : sizeof(ResTable_entry) + sizeof(Res_value);
+ for (auto const v : values) {
+ t.header.size += v ? entry_size : 0;
+ }
- Res_value v1;
- memset(&v1, 0, sizeof(v1));
- t.header.size += sizeof(v1);
+ uint8_t* data = (uint8_t *)malloc(t.header.size);
+ uint8_t* p_header = data;
+ uint8_t* p_offsets = data + t.header.headerSize;
+ uint8_t* p_entries = data + t.entriesStart;
- offsets[1] = ResTable_type::NO_ENTRY;
+ memcpy(p_header, &t, sizeof(t));
- offsets[2] = sizeof(e1) + sizeof(v1);
- ResTable_entry e2;
- memset(&e2, 0, sizeof(e2));
- e2.size = sizeof(e2);
- e2.key.index = 1;
- t.header.size += sizeof(e2);
+ size_t i = 0, entry_offset = 0;
+ uint32_t k = 0;
+ for (auto const& v : values) {
+ if (short_offsets) {
+ uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i;
+ *p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu;
+ } else {
+ uint32_t *p = reinterpret_cast<uint32_t *>(p_offsets) + i;
+ *p = v ? entry_offset : ResTable_type::NO_ENTRY;
+ }
- Res_value v2;
- memset(&v2, 0, sizeof(v2));
- t.header.size += sizeof(v2);
-
- uint8_t* data = (uint8_t*)malloc(t.header.size);
- uint8_t* p = data;
- memcpy(p, &t, sizeof(t));
- p += sizeof(t);
- memcpy(p, offsets, sizeof(offsets));
- p += sizeof(offsets);
- memcpy(p, &e1, sizeof(e1));
- p += sizeof(e1);
- memcpy(p, &v1, sizeof(v1));
- p += sizeof(v1);
- memcpy(p, &e2, sizeof(e2));
- p += sizeof(e2);
- memcpy(p, &v2, sizeof(v2));
- p += sizeof(v2);
- return data;
+ if (v) {
+ ResTable_entry entry{};
+ if (compact_entry) {
+ entry.compact.key = i;
+ entry.compact.flags = ResTable_entry::FLAG_COMPACT | (v->dataType << 8);
+ entry.compact.data = v->data;
+ memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry);
+ entry_offset += sizeof(entry);
+ } else {
+ Res_value value{};
+ entry.full.size = sizeof(entry);
+ entry.full.key.index = i;
+ value = *v;
+ memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry);
+ memcpy(p_entries, &value, sizeof(value)); p_entries += sizeof(value);
+ entry_offset += sizeof(entry) + sizeof(value);
+ }
+ }
+ i++;
+ }
+ return reinterpret_cast<ResTable_type*>(data);
}
TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
- ResTable_type* data = (ResTable_type*) createTypeData();
+ std::vector<Res_value *> values;
- TypeVariant v(data);
+ Res_value *v1 = new Res_value{};
+ values.push_back(v1);
- TypeVariant::iterator iter = v.beginEntries();
- ASSERT_EQ(uint32_t(0), iter.index());
- ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(0), iter->key.index);
- ASSERT_NE(v.endEntries(), iter);
+ values.push_back(nullptr);
- iter++;
+ Res_value *v2 = new Res_value{};
+ values.push_back(v2);
- ASSERT_EQ(uint32_t(1), iter.index());
- ASSERT_TRUE(NULL == *iter);
- ASSERT_NE(v.endEntries(), iter);
+ Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678};
+ values.push_back(v3);
- iter++;
+ // test for combinations of compact_entry and short_offsets
+ for (size_t i = 0; i < 4; i++) {
+ bool compact_entry = i & 0x1, short_offsets = i & 0x2;
+ ResTable_type* data = createTypeTable(values, compact_entry, short_offsets);
+ TypeVariant v(data);
- ASSERT_EQ(uint32_t(2), iter.index());
- ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(1), iter->key.index);
- ASSERT_NE(v.endEntries(), iter);
+ TypeVariant::iterator iter = v.beginEntries();
+ ASSERT_EQ(uint32_t(0), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(uint32_t(0), iter->key());
+ ASSERT_NE(v.endEntries(), iter);
- iter++;
+ iter++;
- ASSERT_EQ(v.endEntries(), iter);
+ ASSERT_EQ(uint32_t(1), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ ASSERT_NE(v.endEntries(), iter);
- free(data);
+ iter++;
+
+ ASSERT_EQ(uint32_t(2), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(uint32_t(2), iter->key());
+ ASSERT_NE(v.endEntries(), iter);
+
+ iter++;
+
+ ASSERT_EQ(uint32_t(3), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(iter->is_compact(), compact_entry);
+ ASSERT_EQ(uint32_t(3), iter->key());
+ ASSERT_EQ(uint32_t(0x12345678), iter->value().data);
+ ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType);
+
+ iter++;
+
+ ASSERT_EQ(v.endEntries(), iter);
+
+ free(data);
+ }
}
} // namespace android
diff --git a/libs/androidfw/tests/data/sparse/Android.bp b/libs/androidfw/tests/data/sparse/Android.bp
new file mode 100644
index 0000000..b0da375c
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_libs_androidfw_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+android_test_helper_app {
+ name: "FrameworkResourcesSparseTestApp",
+ sdk_version: "current",
+ min_sdk_version: "32",
+ aaptflags: [
+ "--enable-sparse-encoding",
+ ],
+}
+
+android_test_helper_app {
+ name: "FrameworkResourcesNotSparseTestApp",
+ sdk_version: "current",
+ min_sdk_version: "32",
+}
diff --git a/libs/androidfw/tests/data/sparse/AndroidManifest.xml b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
index 27911b6..9c23a72 100644
--- a/libs/androidfw/tests/data/sparse/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
@@ -17,4 +17,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.sparse">
<application />
+ <uses-sdk android:minSdkVersion="32" />
</manifest>
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
index 2492dbf..a66e1af 100644
--- a/libs/androidfw/tests/data/sparse/R.h
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -42,7 +42,7 @@
struct string {
enum : uint32_t {
foo_999 = 0x7f0203e7,
- only_v26 = 0x7f0203e8
+ only_land = 0x7f0203e8
};
};
};
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
index 4ea5468..114ecbb 100755
--- a/libs/androidfw/tests/data/sparse/gen_strings.sh
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -1,20 +1,20 @@
#!/bin/bash
OUTPUT_default=res/values/strings.xml
-OUTPUT_v26=res/values-v26/strings.xml
+OUTPUT_land=res/values-land/strings.xml
echo "<resources>" > $OUTPUT_default
-echo "<resources>" > $OUTPUT_v26
+echo "<resources>" > $OUTPUT_land
for i in {0..999}
do
echo " <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default
if [ "$(($i % 3))" -eq "0" ]
then
- echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26
+ echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_land
fi
done
echo "</resources>" >> $OUTPUT_default
-echo " <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26
-echo "</resources>" >> $OUTPUT_v26
+echo " <string name=\"only_land\">only land</string>" >> $OUTPUT_land
+echo "</resources>" >> $OUTPUT_land
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
index b08a621..4d4d4a8 100644
--- a/libs/androidfw/tests/data/sparse/not_sparse.apk
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
similarity index 99%
rename from libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
rename to libs/androidfw/tests/data/sparse/res/values-land/strings.xml
index d116087e..66222c3 100644
--- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
+++ b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
@@ -333,5 +333,5 @@
<string name="foo_993">9930</string>
<string name="foo_996">9960</string>
<string name="foo_999">9990</string>
- <string name="only_v26">only v26</string>
+ <string name="only_land">only land</string>
</resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml b/libs/androidfw/tests/data/sparse/res/values-land/values.xml
similarity index 100%
rename from libs/androidfw/tests/data/sparse/res/values-v26/values.xml
rename to libs/androidfw/tests/data/sparse/res/values-land/values.xml
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
index 9fd01fb..0f2d75a 100644
--- a/libs/androidfw/tests/data/sparse/sparse.apk
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index cccc0f8..c0a4fdf 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -636,7 +636,7 @@
cc_defaults {
name: "hwui_test_defaults",
defaults: ["hwui_defaults"],
- test_suites: ["device-tests"],
+ test_suites: ["general-tests"],
header_libs: ["libandroid_headers_private"],
target: {
android: {
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 381fb9f..911315f 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -16,22 +16,22 @@
<configuration description="Config for hwuimicro">
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
- <option name="push" value="hwui_unit_tests->/data/nativetest/hwui_unit_tests" />
- <option name="push" value="hwuimicro->/data/benchmarktest/hwuimicro" />
- <option name="push" value="hwuimacro->/data/benchmarktest/hwuimacro" />
+ <option name="push" value="hwui_unit_tests->/data/local/tmp/nativetest/hwui_unit_tests" />
+ <option name="push" value="hwuimicro->/data/local/tmp/benchmarktest/hwuimicro" />
+ <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/nativetest" />
+ <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
<option name="module-name" value="hwui_unit_tests" />
</test>
<test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
- <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+ <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
<option name="benchmark-module-name" value="hwuimicro" />
<option name="file-exclusion-filter-regex" value=".*\.config$" />
</test>
<test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
- <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+ <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
<option name="benchmark-module-name" value="hwuimacro" />
<option name="file-exclusion-filter-regex" value=".*\.config$" />
</test>
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index e86b338..41ced8c 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -43,13 +43,13 @@
float backgroundRetentionPercent = 0.5f;
// How long after the last renderer goes away before the GPU context is released. A value
// of 0 means only drop the context on background TRIM signals
- nsecs_t contextTimeout = 0_ms;
+ nsecs_t contextTimeout = 10_s;
// The minimum amount of time to hold onto items in the resource cache
// The actual time used will be the max of this & when frames were actually rendered
nsecs_t minimumResourceRetention = 10_s;
// If false, use only TRIM_UI_HIDDEN to drive background cache limits;
// If true, use all signals (such as all contexts are stopped) to drive the limits
- bool useAlternativeUiHidden = false;
+ bool useAlternativeUiHidden = true;
// Whether or not to only purge scratch resources when triggering UI Hidden or background
// collection
bool purgeScratchOnly = true;
diff --git a/libs/hwui/TEST_MAPPING b/libs/hwui/TEST_MAPPING
index b1719a9..03682e8 100644
--- a/libs/hwui/TEST_MAPPING
+++ b/libs/hwui/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"name": "CtsAccelerationTestCases"
+ },
+ {
+ "name": "hwui_unit_tests"
}
],
"imports": [
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index f603e23..0663121 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -860,6 +860,11 @@
RenderProxy::setRtAnimationsEnabled(enabled);
}
+static void android_view_ThreadedRenderer_notifyCallbackPending(JNIEnv*, jclass, jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->notifyCallbackPending();
+}
+
// Plumbs the display density down to DeviceInfo.
static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) {
// Convert from dpi to density-independent pixels.
@@ -1037,6 +1042,8 @@
{"nIsDrawingEnabled", "()Z", (void*)android_view_ThreadedRenderer_isDrawingEnabled},
{"nSetRtAnimationsEnabled", "(Z)V",
(void*)android_view_ThreadedRenderer_setRtAnimationsEnabled},
+ {"nNotifyCallbackPending", "(J)V",
+ (void*)android_view_ThreadedRenderer_notifyCallbackPending},
};
static JavaVM* mJvm = nullptr;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index dc7676c..cb30614 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -42,6 +42,7 @@
size_t, int64_t);
typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
typedef void (*APH_closeSession)(APerformanceHintSession* session);
bool gAPerformanceHintBindingInitialized = false;
@@ -49,6 +50,7 @@
APH_createSession gAPH_createSessionFn = nullptr;
APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
+APH_sendHint gAPH_sendHintFn = nullptr;
APH_closeSession gAPH_closeSessionFn = nullptr;
void ensureAPerformanceHintBindingInitialized() {
@@ -77,6 +79,10 @@
gAPH_reportActualWorkDurationFn == nullptr,
"Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
+ gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
+ LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
+ "Failed to find required symbol APerformanceHint_sendHint!");
+
gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
"Failed to find required symbol APerformanceHint_closeSession!");
@@ -239,6 +245,16 @@
mLastDequeueBufferDuration = dequeueBufferDuration;
}
+void DrawFrameTask::sendLoadResetHint() {
+ if (!(Properties::useHintManager && Properties::isDrawingEnabled())) return;
+ if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
+ nsecs_t now = systemTime();
+ if (now - mLastFrameNotification > kResetHintTimeout) {
+ mHintSessionWrapper->sendHint(SessionHint::CPU_LOAD_RESET);
+ }
+ mLastFrameNotification = now;
+}
+
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
@@ -327,6 +343,12 @@
}
}
+void DrawFrameTask::HintSessionWrapper::sendHint(SessionHint hint) {
+ if (mHintSession && Properties::isDrawingEnabled()) {
+ gAPH_sendHintFn(mHintSession, static_cast<int>(hint));
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index d6fc292..7eae41c 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -28,6 +28,7 @@
#include "../Rect.h"
#include "../TreeInfo.h"
#include "RenderTask.h"
+#include "utils/TimeUtils.h"
namespace android {
namespace uirenderer {
@@ -90,6 +91,8 @@
void forceDrawNextFrame() { mForceDrawFrame = true; }
+ void sendLoadResetHint();
+
private:
class HintSessionWrapper {
public:
@@ -98,6 +101,7 @@
void updateTargetWorkDuration(long targetDurationNanos);
void reportActualWorkDuration(long actualDurationNanos);
+ void sendHint(SessionHint hint);
private:
APerformanceHintSession* mHintSession = nullptr;
@@ -135,6 +139,9 @@
nsecs_t mLastTargetWorkDuration = 0;
std::optional<HintSessionWrapper> mHintSessionWrapper;
+ nsecs_t mLastFrameNotification = 0;
+ nsecs_t kResetHintTimeout = 100_ms;
+
bool mForceDrawFrame = false;
};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 3324715..03a2bc9 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -236,6 +236,10 @@
mRenderThread.queue().post([this]() { mContext->notifyFramePending(); });
}
+void RenderProxy::notifyCallbackPending() {
+ mDrawFrameTask.sendLoadResetHint();
+}
+
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
mRenderThread.queue().runSync([&]() {
std::lock_guard lock(mRenderThread.getJankDataMutex());
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 2a99a73..a21faa8 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -109,6 +109,7 @@
static int maxTextureSize();
void stopDrawing();
void notifyFramePending();
+ void notifyCallbackPending();
void dumpProfileInfo(int fd, int dumpFlags);
// Not exported, only used for testing
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index df06ead..508e198 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -32,7 +32,8 @@
return cacheUsage;
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
+// TOOD(258700630): fix this test and re-enable
+RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
int32_t width = DeviceInfo::get()->getWidth();
int32_t height = DeviceInfo::get()->getHeight();
GrDirectContext* grContext = renderThread.getGrContext();
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index 098b4cc..92fd829 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -14,17 +14,17 @@
* limitations under the License.
*/
+#include <android-base/macros.h>
#include <gtest/gtest.h>
-
-#include "protos/graphicsstats.pb.h"
-#include "service/GraphicsStatsService.h"
-
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include "protos/graphicsstats.pb.h"
+#include "service/GraphicsStatsService.h"
+
using namespace android;
using namespace android::uirenderer;
@@ -49,11 +49,7 @@
// No code left untested
TEST(GraphicsStats, findRootPath) {
-#ifdef __LP64__
- std::string expected = "/data/nativetest64/hwui_unit_tests";
-#else
- std::string expected = "/data/nativetest/hwui_unit_tests";
-#endif
+ std::string expected = "/data/local/tmp/nativetest/hwui_unit_tests/" ABI_STRING;
EXPECT_EQ(expected, findRootPath());
}
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 54f893e..099efd3 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -22,10 +22,18 @@
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkColor.h>
+#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
+#include <ftl/enum.h>
+
+#include <mutex>
#include "PointerControllerContext.h"
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+
namespace android {
namespace {
@@ -223,7 +231,7 @@
}
void PointerController::clearSpotsLocked() {
- for (auto& [displayID, spotController] : mLocked.spotControllers) {
+ for (auto& [displayId, spotController] : mLocked.spotControllers) {
spotController.clearSpots();
}
}
@@ -235,7 +243,7 @@
void PointerController::reloadPointerResources() {
std::scoped_lock lock(getLock());
- for (auto& [displayID, spotController] : mLocked.spotControllers) {
+ for (auto& [displayId, spotController] : mLocked.spotControllers) {
spotController.reloadSpotResources();
}
@@ -286,13 +294,13 @@
std::scoped_lock lock(getLock());
for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
- int32_t displayID = it->first;
- if (!displayIdSet.count(displayID)) {
+ int32_t displayId = it->first;
+ if (!displayIdSet.count(displayId)) {
/*
* Ensures that an in-progress animation won't dereference
* a null pointer to TouchSpotController.
*/
- mContext.removeAnimationCallback(displayID);
+ mContext.removeAnimationCallback(displayId);
it = mLocked.spotControllers.erase(it);
} else {
++it;
@@ -313,4 +321,20 @@
return it != di.end() ? it->transform : kIdentityTransform;
}
+void PointerController::dump(std::string& dump) {
+ dump += INDENT "PointerController:\n";
+ std::scoped_lock lock(getLock());
+ dump += StringPrintf(INDENT2 "Presentation: %s\n",
+ ftl::enum_string(mLocked.presentation).c_str());
+ dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId);
+ dump += StringPrintf(INDENT2 "Viewports:\n");
+ for (const auto& info : mLocked.mDisplayInfos) {
+ info.dump(dump, INDENT3);
+ }
+ dump += INDENT2 "Spot Controllers:\n";
+ for (const auto& [_, spotController] : mLocked.spotControllers) {
+ spotController.dump(dump, INDENT3);
+ }
+}
+
} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 33480e8..48d5a57 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -27,6 +27,7 @@
#include <map>
#include <memory>
+#include <string>
#include <vector>
#include "MouseCursorController.h"
@@ -75,6 +76,8 @@
void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos)
REQUIRES(getLock());
+ void dump(std::string& dump);
+
protected:
using WindowListenerConsumer =
std::function<void(const sp<android::gui::WindowInfosListener>&)>;
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 4ac66c4..d9fe599 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -21,8 +21,15 @@
#include "TouchSpotController.h"
+#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
#include <log/log.h>
+#include <mutex>
+
+#define INDENT " "
+#define INDENT2 " "
+
namespace {
// Time to spend fading out the spot completely.
const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
@@ -53,6 +60,12 @@
}
}
+void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const {
+ out += prefix;
+ base::StringAppendF(&out, "Spot{id=%" PRIx32 ", alpha=%f, scale=%f, pos=[%f, %f]}\n", id, alpha,
+ scale, x, y);
+}
+
// --- TouchSpotController ---
TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
@@ -255,4 +268,22 @@
mContext.addAnimationCallback(mDisplayId, func);
}
+void TouchSpotController::dump(std::string& out, const char* prefix) const {
+ using base::StringAppendF;
+ out += prefix;
+ out += "SpotController:\n";
+ out += prefix;
+ StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId);
+ std::scoped_lock lock(mLock);
+ out += prefix;
+ StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating));
+ out += prefix;
+ out += INDENT "Spots:\n";
+ std::string spotPrefix = prefix;
+ spotPrefix += INDENT2;
+ for (const auto& spot : mLocked.displaySpots) {
+ spot->dump(out, spotPrefix.c_str());
+ }
+}
+
} // namespace android
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 703de36..5bbc75d 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -38,6 +38,8 @@
void reloadSpotResources();
bool doAnimations(nsecs_t timestamp);
+ void dump(std::string& out, const char* prefix = "") const;
+
private:
struct Spot {
static const uint32_t INVALID_ID = 0xffffffff;
@@ -58,6 +60,7 @@
mLastIcon(nullptr) {}
void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+ void dump(std::string& out, const char* prefix = "") const;
private:
const SpriteIcon* mLastIcon;
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 4f45602..a6da0a3 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -25,6 +25,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -125,7 +126,7 @@
* @hide
*/
public static GnssCapabilities empty() {
- return new GnssCapabilities(0, 0, 0, new ArrayList<>());
+ return new GnssCapabilities(0, 0, 0, Collections.emptyList());
}
private final @TopHalCapabilityFlags int mTopFlags;
@@ -142,7 +143,7 @@
mTopFlags = topFlags;
mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
mPowerFlags = powerFlags;
- mGnssSignalTypes = gnssSignalTypes;
+ mGnssSignalTypes = Collections.unmodifiableList(gnssSignalTypes);
}
/**
@@ -155,7 +156,7 @@
return this;
} else {
return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
- new ArrayList<>(mGnssSignalTypes));
+ mGnssSignalTypes);
}
}
@@ -171,7 +172,7 @@
return this;
} else {
return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
- new ArrayList<>(mGnssSignalTypes));
+ mGnssSignalTypes);
}
}
@@ -186,7 +187,7 @@
return this;
} else {
return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
- new ArrayList<>(mGnssSignalTypes));
+ mGnssSignalTypes);
}
}
@@ -606,7 +607,7 @@
mTopFlags = 0;
mMeasurementCorrectionsFlags = 0;
mPowerFlags = 0;
- mGnssSignalTypes = new ArrayList<>();
+ mGnssSignalTypes = Collections.emptyList();
}
public Builder(@NonNull GnssCapabilities capabilities) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d975e96..17d7045 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2328,10 +2328,9 @@
return AudioSystem.SUCCESS;
}
- private final Map<Integer, Object> mDevRoleForCapturePresetListeners = new HashMap<>(){{
- put(AudioSystem.DEVICE_ROLE_PREFERRED,
- new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
- }};
+ private final Map<Integer, Object> mDevRoleForCapturePresetListeners = Map.of(
+ AudioSystem.DEVICE_ROLE_PREFERRED,
+ new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
private class DevRoleListenerInfo<T> {
final @NonNull Executor mExecutor;
@@ -6515,15 +6514,17 @@
// AudioPort implementation
//
- static final int AUDIOPORT_GENERATION_INIT = 0;
- static Integer sAudioPortGeneration = new Integer(AUDIOPORT_GENERATION_INIT);
- static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
- static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
- static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
+ private static final int AUDIOPORT_GENERATION_INIT = 0;
+ private static Object sAudioPortGenerationLock = new Object();
+ @GuardedBy("sAudioPortGenerationLock")
+ private static int sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
+ private static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
+ private static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
+ private static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
static int resetAudioPortGeneration() {
int generation;
- synchronized (sAudioPortGeneration) {
+ synchronized (sAudioPortGenerationLock) {
generation = sAudioPortGeneration;
sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
}
@@ -6533,7 +6534,7 @@
static int updateAudioPortCache(ArrayList<AudioPort> ports, ArrayList<AudioPatch> patches,
ArrayList<AudioPort> previousPorts) {
sAudioPortEventHandler.init();
- synchronized (sAudioPortGeneration) {
+ synchronized (sAudioPortGenerationLock) {
if (sAudioPortGeneration == AUDIOPORT_GENERATION_INIT) {
int[] patchGeneration = new int[1];
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index ca175b4..0f962f9 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -30,6 +30,7 @@
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -446,14 +447,13 @@
// BaseMap is corresponding to audio_utils::metadata::Data
private static final int AUDIO_METADATA_OBJ_TYPE_BASEMAP = 6;
- private static final HashMap<Class, Integer> AUDIO_METADATA_OBJ_TYPES = new HashMap<>() {{
- put(Integer.class, AUDIO_METADATA_OBJ_TYPE_INT);
- put(Long.class, AUDIO_METADATA_OBJ_TYPE_LONG);
- put(Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT);
- put(Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE);
- put(String.class, AUDIO_METADATA_OBJ_TYPE_STRING);
- put(BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
- }};
+ private static final Map<Class, Integer> AUDIO_METADATA_OBJ_TYPES = Map.of(
+ Integer.class, AUDIO_METADATA_OBJ_TYPE_INT,
+ Long.class, AUDIO_METADATA_OBJ_TYPE_LONG,
+ Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT,
+ Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE,
+ String.class, AUDIO_METADATA_OBJ_TYPE_STRING,
+ BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
private static final Charset AUDIO_METADATA_CHARSET = StandardCharsets.UTF_8;
@@ -634,8 +634,8 @@
* Datum corresponds to Object
****************************************************************************************/
- private static final HashMap<Integer, DataPackage<?>> DATA_PACKAGES = new HashMap<>() {{
- put(AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
+ private static final Map<Integer, DataPackage<?>> DATA_PACKAGES = Map.of(
+ AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
@Override
@Nullable
public Integer unpack(ByteBuffer buffer) {
@@ -647,8 +647,8 @@
output.putInt(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
@Override
@Nullable
public Long unpack(ByteBuffer buffer) {
@@ -660,8 +660,8 @@
output.putLong(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
@Override
@Nullable
public Float unpack(ByteBuffer buffer) {
@@ -673,8 +673,8 @@
output.putFloat(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
@Override
@Nullable
public Double unpack(ByteBuffer buffer) {
@@ -686,8 +686,8 @@
output.putDouble(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
@Override
@Nullable
public String unpack(ByteBuffer buffer) {
@@ -713,9 +713,9 @@
output.put(valueArr);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
- }};
+ },
+ AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
+
// ObjectPackage is a special case that it is expected to unpack audio_utils::metadata::Datum,
// which contains data type and data size besides the payload for the data.
private static final ObjectPackage OBJECT_PACKAGE = new ObjectPackage();
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 6764890..ad6acea 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -438,6 +438,18 @@
return "AUDIO_FORMAT_APTX_TWSP";
case /* AUDIO_FORMAT_LC3 */ 0x2B000000:
return "AUDIO_FORMAT_LC3";
+ case /* AUDIO_FORMAT_MPEGH */ 0x2C000000:
+ return "AUDIO_FORMAT_MPEGH";
+ case /* AUDIO_FORMAT_IEC60958 */ 0x2D000000:
+ return "AUDIO_FORMAT_IEC60958";
+ case /* AUDIO_FORMAT_DTS_UHD */ 0x2E000000:
+ return "AUDIO_FORMAT_DTS_UHD";
+ case /* AUDIO_FORMAT_DRA */ 0x2F000000:
+ return "AUDIO_FORMAT_DRA";
+ case /* AUDIO_FORMAT_APTX_ADAPTIVE_QLEA */ 0x30000000:
+ return "AUDIO_FORMAT_APTX_ADAPTIVE_QLEA";
+ case /* AUDIO_FORMAT_APTX_ADAPTIVE_R4 */ 0x31000000:
+ return "AUDIO_FORMAT_APTX_ADAPTIVE_R4";
/* Aliases */
case /* AUDIO_FORMAT_PCM_16_BIT */ 0x1:
@@ -510,10 +522,14 @@
return "AUDIO_FORMAT_MAT_2_0"; // (MAT | MAT_SUB_2_0)
case /* AUDIO_FORMAT_MAT_2_1 */ 0x24000003:
return "AUDIO_FORMAT_MAT_2_1"; // (MAT | MAT_SUB_2_1)
- case /* AUDIO_FORMAT_DTS_UHD */ 0x2E000000:
- return "AUDIO_FORMAT_DTS_UHD";
- case /* AUDIO_FORMAT_DRA */ 0x2F000000:
- return "AUDIO_FORMAT_DRA";
+ case /* AUDIO_FORMAT_MPEGH_SUB_BL_L3 */ 0x2C000013:
+ return "AUDIO_FORMAT_MPEGH_SUB_BL_L3";
+ case /* AUDIO_FORMAT_MPEGH_SUB_BL_L4 */ 0x2C000014:
+ return "AUDIO_FORMAT_MPEGH_SUB_BL_L4";
+ case /* AUDIO_FORMAT_MPEGH_SUB_LC_L3 */ 0x2C000023:
+ return "AUDIO_FORMAT_MPEGH_SUB_LC_L3";
+ case /* AUDIO_FORMAT_MPEGH_SUB_LC_L4 */ 0x2C000024:
+ return "AUDIO_FORMAT_MPEGH_SUB_LC_L4";
default:
return "AUDIO_FORMAT_(" + audioFormat + ")";
}
@@ -2407,4 +2423,3 @@
*/
final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
}
-
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 85cd342..d51f1e1 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -48,8 +48,8 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.NioUtils;
-import java.util.HashMap;
import java.util.LinkedList;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -1867,26 +1867,24 @@
}
// General pair map
- private static final HashMap<String, Integer> CHANNEL_PAIR_MAP = new HashMap<>() {{
- put("front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
- | AudioFormat.CHANNEL_OUT_FRONT_RIGHT);
- put("back", AudioFormat.CHANNEL_OUT_BACK_LEFT
- | AudioFormat.CHANNEL_OUT_BACK_RIGHT);
- put("front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
- | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
- put("side", AudioFormat.CHANNEL_OUT_SIDE_LEFT
- | AudioFormat.CHANNEL_OUT_SIDE_RIGHT);
- put("top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
- | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT);
- put("top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
- | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT);
- put("top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
- | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT);
- put("bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
- | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT);
- put("front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
- | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
- }};
+ private static final Map<String, Integer> CHANNEL_PAIR_MAP = Map.of(
+ "front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
+ | AudioFormat.CHANNEL_OUT_FRONT_RIGHT,
+ "back", AudioFormat.CHANNEL_OUT_BACK_LEFT
+ | AudioFormat.CHANNEL_OUT_BACK_RIGHT,
+ "front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
+ | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER,
+ "side", AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT,
+ "top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
+ | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT,
+ "top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
+ | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT,
+ "top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
+ | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT,
+ "bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
+ | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT,
+ "front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
+ | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
/**
* Convenience method to check that the channel configuration (a.k.a channel mask) is supported
@@ -1924,7 +1922,7 @@
return false;
}
// Check all pairs to see that they are matched (front duplicated here).
- for (HashMap.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
+ for (Map.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
final int positionPair = e.getValue();
if ((channelConfig & positionPair) != 0
&& (channelConfig & positionPair) != positionPair) {
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 0c8cacd..524bde4 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -70,6 +70,7 @@
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
@@ -4836,12 +4837,13 @@
for (int i = 1; i < entryValues.length; ++i) {
final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]);
int first = -1, second = -1;
- if (guessDataFormat.first == dataFormat.first
- || guessDataFormat.second == dataFormat.first) {
+ if (Objects.equals(guessDataFormat.first, dataFormat.first)
+ || Objects.equals(guessDataFormat.second, dataFormat.first)) {
first = dataFormat.first;
}
- if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second
- || guessDataFormat.second == dataFormat.second)) {
+ if (dataFormat.second != -1
+ && (Objects.equals(guessDataFormat.first, dataFormat.second)
+ || Objects.equals(guessDataFormat.second, dataFormat.second))) {
second = dataFormat.second;
}
if (first == -1 && second == -1) {
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
index 3008067..2342a42 100644
--- a/media/java/android/media/MediaHTTPService.java
+++ b/media/java/android/media/MediaHTTPService.java
@@ -21,6 +21,8 @@
import android.os.IBinder;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookieStore;
@@ -31,7 +33,9 @@
public class MediaHTTPService extends IMediaHTTPService.Stub {
private static final String TAG = "MediaHTTPService";
@Nullable private List<HttpCookie> mCookies;
- private Boolean mCookieStoreInitialized = new Boolean(false);
+ private final Object mCookieStoreInitializedLock = new Object();
+ @GuardedBy("mCookieStoreInitializedLock")
+ private boolean mCookieStoreInitialized = false;
public MediaHTTPService(@Nullable List<HttpCookie> cookies) {
mCookies = cookies;
@@ -40,7 +44,7 @@
public IMediaHTTPConnection makeHTTPConnection() {
- synchronized (mCookieStoreInitialized) {
+ synchronized (mCookieStoreInitializedLock) {
// Only need to do it once for all connections
if ( !mCookieStoreInitialized ) {
CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -78,8 +82,8 @@
Log.v(TAG, "makeHTTPConnection(" + this + "): cookieHandler: " + cookieHandler +
" Cookies: " + mCookies);
- } // mCookieStoreInitialized
- } // synchronized
+ }
+ }
return new MediaHTTPConnection();
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 77b5746..79a5902 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2507,6 +2507,8 @@
*
* @see android.media.MediaPlayer#getTrackInfo
*/
+ // The creator needs to be pulic, which requires removing the @UnsupportedAppUsage
+ @SuppressWarnings("ParcelableCreator")
static public class TrackInfo implements Parcelable {
/**
* Gets the track type.
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 2e7896e..b57476f 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -387,7 +387,7 @@
try {
mMediaRouterService.registerClientGroupId(mClient, groupId);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to register group ID of the client.", ex);
+ ex.rethrowFromSystemServer();
}
}
}
@@ -439,7 +439,7 @@
try {
mMediaRouterService.unregisterClient(mClient);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router client.", ex);
+ ex.rethrowFromSystemServer();
}
mClient = null;
}
@@ -466,7 +466,7 @@
mMediaRouterService.setDiscoveryRequest(mClient,
mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to publish media router client discovery request.", ex);
+ ex.rethrowFromSystemServer();
}
}
}
@@ -478,7 +478,7 @@
mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null,
explicit);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to publish media router client selected route.", ex);
+ ex.rethrowFromSystemServer();
}
}
}
@@ -490,7 +490,7 @@
try {
mClientState = mMediaRouterService.getState(mClient);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to retrieve media router client state.", ex);
+ ex.rethrowFromSystemServer();
}
}
final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes =
@@ -535,7 +535,7 @@
mMediaRouterService.requestSetVolume(mClient,
route.mGlobalRouteId, volume);
} catch (RemoteException ex) {
- Log.w(TAG, "Unable to request volume change.", ex);
+ ex.rethrowFromSystemServer();
}
}
}
@@ -546,7 +546,7 @@
mMediaRouterService.requestUpdateVolume(mClient,
route.mGlobalRouteId, direction);
} catch (RemoteException ex) {
- Log.w(TAG, "Unable to request volume change.", ex);
+ ex.rethrowFromSystemServer();
}
}
}
@@ -653,7 +653,7 @@
try {
return mMediaRouterService.isPlaybackActive(mClient);
} catch (RemoteException ex) {
- Log.e(TAG, "Unable to retrieve playback active state.", ex);
+ ex.rethrowFromSystemServer();
}
}
return false;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b4b908d..161ea25 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -603,7 +603,7 @@
*/
public void transferTo(@NonNull MediaRoute2Info route) {
if (isSystemRouter()) {
- sManager.selectRoute(mClientPackageName, route);
+ sManager.transfer(mClientPackageName, route);
return;
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index b6f07f4..e403e24 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -448,14 +448,16 @@
}
/**
- * Selects media route for the specified package name.
+ * Transfers a {@link RoutingSessionInfo routing session} belonging to a specified package name
+ * to a {@link MediaRoute2Info media route}.
+ *
+ * <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing
+ * session based on the provided package name.
*/
- public void selectRoute(@NonNull String packageName, @NonNull MediaRoute2Info route) {
+ public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) {
Objects.requireNonNull(packageName, "packageName must not be null");
Objects.requireNonNull(route, "route must not be null");
- Log.v(TAG, "Selecting route. packageName= " + packageName + ", route=" + route);
-
List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
transfer(targetSession, route);
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 82c3139..538e64c 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -158,13 +158,15 @@
/**
* Creates a local media player for the ringtone using currently set attributes.
+ * @return true if media player creation succeeded or is deferred,
+ * false if it did not succeed and can't be tried remotely.
* @hide
*/
- public void createLocalMediaPlayer() {
+ public boolean createLocalMediaPlayer() {
Trace.beginSection("createLocalMediaPlayer");
if (mUri == null) {
Log.e(TAG, "Could not create media player as no URI was provided.");
- return;
+ return mAllowRemote && mRemotePlayer != null;
}
destroyLocalPlayer();
// try opening uri locally before delegating to remote player
@@ -195,6 +197,30 @@
}
}
Trace.endSection();
+ return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null);
+ }
+
+ /**
+ * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
+ * If the ringtone has not been created, it will load based on URI provided at {@link #setUri}
+ * and if not URI has been set, it will assume no haptic channels are present.
+ * @hide
+ */
+ public boolean hasHapticChannels() {
+ // FIXME: support remote player, or internalize haptic channels support and remove entirely.
+ try {
+ android.os.Trace.beginSection("Ringtone.hasHapticChannels");
+ if (mLocalPlayer != null) {
+ for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
+ if (trackInfo.hasHapticChannels()) {
+ return true;
+ }
+ }
+ }
+ } finally {
+ android.os.Trace.endSection();
+ }
+ return false;
}
/**
@@ -423,7 +449,6 @@
*/
public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) {
mVolumeShaperConfig = volumeShaperConfig;
-
mUri = uri;
if (mUri == null) {
destroyLocalPlayer();
@@ -443,10 +468,11 @@
if (mLocalPlayer != null) {
// Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
// (typically because ringer mode is vibrate).
- boolean isHapticOnly = AudioManager.hasHapticChannels(mContext, mUri)
- && !mAudioAttributes.areHapticChannelsMuted() && mVolume == 0;
- if (isHapticOnly || mAudioManager.getStreamVolume(
- AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) {
+ if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
+ != 0) {
+ startLocalPlayer();
+ } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) {
+ // is haptic only ringtone
startLocalPlayer();
}
} else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 27db41c..f15f443 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -720,11 +720,14 @@
@Nullable VolumeShaper.Configuration volumeShaperConfig,
AudioAttributes audioAttributes) {
// Don't set the stream type
- Ringtone ringtone =
- getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, false);
+ Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
+ volumeShaperConfig, false);
if (ringtone != null) {
ringtone.setAudioAttributesField(audioAttributes);
- ringtone.createLocalMediaPlayer();
+ if (!ringtone.createLocalMediaPlayer()) {
+ Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
+ return null;
+ }
}
return ringtone;
}
@@ -750,19 +753,6 @@
createLocalMediaPlayer);
}
- //FIXME bypass the notion of stream types within the class
- /**
- * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI on
- * the given stream type. Normally, if you change the stream type on the returned
- * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
- * an optimized route to avoid that.
- *
- * @param streamType The stream type for the ringtone, or -1 if it should
- * not be set (and the default used instead).
- * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
- * @see #getRingtone(Context, Uri)
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
@Nullable VolumeShaper.Configuration volumeShaperConfig,
boolean createLocalMediaPlayer) {
@@ -776,7 +766,10 @@
r.setVolumeShaperConfig(volumeShaperConfig);
r.setUri(ringtoneUri, volumeShaperConfig);
if (createLocalMediaPlayer) {
- r.createLocalMediaPlayer();
+ if (!r.createLocalMediaPlayer()) {
+ Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
+ return null;
+ }
}
return r;
} catch (Exception ex) {
@@ -1158,24 +1151,38 @@
}
// Try finding the scanned ringtone
- final String filename = getDefaultRingtoneFilename(type);
- final String whichAudio = getQueryStringForType(type);
- final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?";
- final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
- try (Cursor cursor = context.getContentResolver().query(baseUri,
- new String[] { MediaColumns._ID },
- where,
- new String[] { filename, "1" }, null)) {
- if (cursor.moveToFirst()) {
- final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse(
- ContentUris.withAppendedId(baseUri, cursor.getLong(0)));
- RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
- Settings.System.putInt(context.getContentResolver(), setting, 1);
- }
+ Uri ringtoneUri = computeDefaultRingtoneUri(context, type);
+ if (ringtoneUri != null) {
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
+ Settings.System.putInt(context.getContentResolver(), setting, 1);
}
}
}
+ /**
+ * @param type the type of ringtone (e.g {@link #TYPE_RINGTONE})
+ * @return the system default URI if found, null otherwise.
+ */
+ private static Uri computeDefaultRingtoneUri(@NonNull Context context, int type) {
+ // Try finding the scanned ringtone
+ final String filename = getDefaultRingtoneFilename(type);
+ final String whichAudio = getQueryStringForType(type);
+ final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?";
+ final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
+ try (Cursor cursor = context.getContentResolver().query(baseUri,
+ new String[] { MediaColumns._ID },
+ where,
+ new String[] { filename, "1" }, null)) {
+ if (cursor.moveToFirst()) {
+ final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse(
+ ContentUris.withAppendedId(baseUri, cursor.getLong(0)));
+ return ringtoneUri;
+ }
+ }
+
+ return null;
+ }
+
private static String getDefaultRingtoneSetting(int type) {
switch (type) {
case TYPE_RINGTONE: return "ringtone_set";
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1bd12af..7e1bbe3 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -244,12 +244,9 @@
mCallback = null;
return;
}
- if (handler == null) {
- handler = new Handler();
- }
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
callback.mSession = this;
- CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
- callback);
+ CallbackMessageHandler msgHandler = new CallbackMessageHandler(looper, callback);
mCallback = msgHandler;
}
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 6ae7dfb..9b8ec5e 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -45,6 +45,7 @@
void onRequestTrackInfoList(int seq);
void onRequestCurrentTvInputId(int seq);
void onRequestStartRecording(in Uri programUri, int seq);
+ void onRequestStopRecording(in String recordingId, int seq);
void onRequestSigning(
in String id, in String algorithm, in String alias, in byte[] data, int seq);
void onAdRequest(in AdRequest request, int Seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 84b9c9e..38fc717 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -64,6 +64,7 @@
void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
+ void notifyRecordingStopped(in IBinder sessionToken, in String recordingId, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 95b4ffa..9e33536 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -54,6 +54,7 @@
void notifyContentBlocked(in String rating);
void notifySignalStrength(int strength);
void notifyRecordingStarted(in String recordingId);
+ void notifyRecordingStopped(in String recordingId);
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 6478057..4ce5871 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -44,6 +44,7 @@
void onRequestTrackInfoList();
void onRequestCurrentTvInputId();
void onRequestStartRecording(in Uri programUri);
+ void onRequestStopRecording(in String recordingId);
void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
void onAdRequest(in AdRequest request);
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 042cb15..a2fdfe0 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -82,6 +82,7 @@
private static final int DO_RELAYOUT_MEDIA_VIEW = 28;
private static final int DO_REMOVE_MEDIA_VIEW = 29;
private static final int DO_NOTIFY_RECORDING_STARTED = 30;
+ private static final int DO_NOTIFY_RECORDING_STOPPED = 31;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -169,6 +170,10 @@
mSessionImpl.notifyRecordingStarted((String) msg.obj);
break;
}
+ case DO_NOTIFY_RECORDING_STOPPED: {
+ mSessionImpl.notifyRecordingStopped((String) msg.obj);
+ break;
+ }
case DO_SEND_SIGNING_RESULT: {
SomeArgs args = (SomeArgs) msg.obj;
mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
@@ -392,6 +397,12 @@
}
@Override
+ public void notifyRecordingStopped(String recordingId) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+ DO_NOTIFY_RECORDING_STOPPED, recordingId));
+ }
+
+ @Override
public void setSurface(Surface surface) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7d84fb2..287df40 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -499,6 +499,18 @@
}
@Override
+ public void onRequestStopRecording(String recordingId, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestStopRecording(recordingId);
+ }
+ }
+
+ @Override
public void onRequestSigning(
String id, String algorithm, String alias, byte[] data, int seq) {
synchronized (mSessionCallbackRecordMap) {
@@ -1059,6 +1071,18 @@
}
}
+ void notifyRecordingStopped(String recordingId) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyRecordingStopped(mToken, recordingId, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -1729,6 +1753,15 @@
});
}
+ void postRequestStopRecording(String recordingId) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestStopRecording(mSession, recordingId);
+ }
+ });
+ }
+
void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
mHandler.post(new Runnable() {
@Override
@@ -1884,11 +1917,22 @@
* called.
*
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+ * @param programUri The Uri of the program to be recorded.
*/
public void onRequestStartRecording(Session session, Uri programUri) {
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#RequestStopRecording} is
+ * called.
+ *
+ * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+ * @param recordingId The recordingId of the recording to be stopped.
+ */
+ public void onRequestStopRecording(Session session, String recordingId) {
+ }
+
+ /**
* This is called when
* {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
* called.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 2956a0a..90eed9e 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -463,6 +463,16 @@
}
/**
+ * Receives stopped recording's ID.
+ *
+ * @param recordingId The ID of the recording stopped
+ * @hide
+ */
+ public void onRecordingStopped(@NonNull String recordingId) {
+ }
+
+
+ /**
* Receives signing result.
* @param signingId the ID to identify the request. It's the same as the corresponding ID in
* {@link Session#requestSigning(String, String, String, byte[])}
@@ -942,6 +952,33 @@
}
/**
+ * Requests starting of recording
+ *
+ * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+ * call {@link android.media.tv.TvRecordingClient#stopRecording()}.
+ * @see android.media.tv.TvRecordingClient#stopRecording()
+ *
+ * @hide
+ */
+ @CallSuper
+ public void requestStopRecording(@NonNull String recordingId) {
+ executeOrPostRunnableOnMainThread(() -> {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestStopRecording");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestStopRecording(recordingId);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestStopRecording", e);
+ }
+ });
+ }
+
+
+
+ /**
* Requests signing of the given data.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -1151,11 +1188,21 @@
onAdResponse(response);
}
+ /**
+ * Calls {@link #onRecordingStarted(String)}.
+ */
void notifyRecordingStarted(String recordingId) {
onRecordingStarted(recordingId);
}
/**
+ * Calls {@link #onRecordingStopped(String)}.
+ */
+ void notifyRecordingStopped(String recordingId) {
+ onRecordingStopped(recordingId);
+ }
+
+ /**
* Notifies when the session state is changed.
*
* @param state the current session state.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 1f270d0..fcd781b 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -581,9 +581,10 @@
}
/**
- * Alerts the TV interactive app that a recording has been started with recordingId
+ * Alerts the TV interactive app that a recording has been started.
*
- * @param recordingId The ID of the recording started
+ * @param recordingId The ID of the recording started. This ID is created and maintained by the
+ * TV app and is used to identify the recording in the future.
*/
public void notifyRecordingStarted(@NonNull String recordingId) {
if (DEBUG) {
@@ -595,6 +596,23 @@
}
/**
+ * Alerts the TV interactive app that a recording has been stopped.
+ *
+ * @param recordingId The ID of the recording stopped. This ID is created and maintained
+ * by the TV app when a recording is started.
+ * @see TvInteractiveAppView#notifyRecordingStarted(String)
+ * @hide
+ */
+ public void notifyRecordingStopped(@NonNull String recordingId) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyRecordingStopped");
+ }
+ if (mSession != null) {
+ mSession.notifyRecordingStopped(recordingId);
+ }
+ }
+
+ /**
* Sends signing result to related TV interactive app.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -867,6 +885,19 @@
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#requestStopRecording()}
+ * is called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param recordingId The ID of the recording to stop.
+ * @hide
+ */
+ public void onRequestStopRecording(
+ @NonNull String iAppServiceId,
+ @NonNull String recordingId) {
+ }
+
+ /**
* This is called when
* {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
* called.
@@ -1204,6 +1235,20 @@
}
@Override
+ public void onRequestStopRecording(Session session, String recordingId) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestStopRecording");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestStopRecording - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onRequestStopRecording(mIAppServiceId, recordingId);
+ }
+ }
+
+ @Override
public void onRequestSigning(
Session session, String id, String algorithm, String alias, byte[] data) {
if (DEBUG) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 51b976b..fab63aa 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -2376,19 +2376,20 @@
}
/**
- * Request a frontend by frontend id.
+ * Request a frontend by frontend info.
*
* <p> This API is used if the applications want to select a desired frontend before
* {@link tune} to use a specific satellite or sending SatCR DiSEqC command for {@link tune}.
*
- * @param desiredId the desired fronted Id. It can be retrieved by
+ * @param desiredFrontendInfo the FrontendInfo of the desired fronted. It can be retrieved by
* {@link getAvailableFrontendInfos}
*
* @return result status of open operation.
* @throws SecurityException if the caller does not have appropriate permissions.
*/
@Result
- public int requestFrontendById(int desiredId) {
+ public int applyFrontend(@NonNull FrontendInfo desiredFrontendInfo) {
+ Objects.requireNonNull(desiredFrontendInfo, "desiredFrontendInfo must not be null");
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
@@ -2399,17 +2400,12 @@
Log.e(TAG, "A frontend has been opened before");
return RESULT_INVALID_STATE;
}
- FrontendInfo frontendInfo = getFrontendInfoById(desiredId);
- if (frontendInfo == null) {
- Log.e(TAG, "Failed to get a FrontendInfo by frontend id: " + desiredId);
- return RESULT_UNAVAILABLE;
- }
- int frontendType = frontendInfo.getType();
+ mFrontendType = desiredFrontendInfo.getType();
+ mDesiredFrontendId = desiredFrontendInfo.getId();
if (DEBUG) {
- Log.d(TAG, "Opening frontend with type " + frontendType + ", id " + desiredId);
+ Log.d(TAG, "Applying frontend with type " + mFrontendType + ", id "
+ + mDesiredFrontendId);
}
- mFrontendType = frontendType;
- mDesiredFrontendId = desiredId;
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
return RESULT_UNAVAILABLE;
}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 1a65832..4bcc3c6 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -28,6 +28,7 @@
import android.os.Process;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
import java.util.concurrent.Executor;
@@ -48,7 +49,9 @@
private static int sInstantId = 0;
private int mSegmentId = 0;
private int mOverflow;
- private Boolean mIsStopped = true;
+ private final Object mIsStoppedLock = new Object();
+ @GuardedBy("mIsStoppedLock")
+ private boolean mIsStopped = true;
private final Object mListenerLock = new Object();
private native int nativeAttachFilter(Filter filter);
@@ -178,7 +181,7 @@
.write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0);
- synchronized (mIsStopped) {
+ synchronized (mIsStoppedLock) {
int result = nativeStartDvr();
if (result == Tuner.RESULT_SUCCESS) {
mIsStopped = false;
@@ -201,7 +204,7 @@
.write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow);
- synchronized (mIsStopped) {
+ synchronized (mIsStoppedLock) {
int result = nativeStopDvr();
if (result == Tuner.RESULT_SUCCESS) {
mIsStopped = true;
@@ -219,7 +222,7 @@
*/
@Result
public int flush() {
- synchronized (mIsStopped) {
+ synchronized (mIsStoppedLock) {
if (mIsStopped) {
return nativeFlushDvr();
}
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index aff2e1b4..89e5e0d 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -230,7 +230,7 @@
case MtpConstants.PROPERTY_PERSISTENT_UID:
// The persistent uid must be unique and never reused among all objects,
// and remain the same between sessions.
- long puid = (object.getPath().toString().hashCode() << 32)
+ long puid = (((long) object.getPath().toString().hashCode()) << 32)
+ object.getModifiedTime();
list.append(id, property.code, property.type, puid);
break;
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c18edcd..a028c04 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -360,6 +360,7 @@
lnb,
gFields.onLnbEventID,
(jint)lnbEventType);
+ env->DeleteLocalRef(lnb);
} else {
ALOGE("LnbClientCallbackImpl::onEvent:"
"Lnb object has been freed. Ignoring callback.");
@@ -378,6 +379,8 @@
lnb,
gFields.onLnbDiseqcMessageID,
array);
+ env->DeleteLocalRef(lnb);
+ env->DeleteLocalRef(array);
} else {
ALOGE("LnbClientCallbackImpl::onDiseqcMessage:"
"Lnb object has been freed. Ignoring callback.");
@@ -404,6 +407,7 @@
jobject dvr(env->NewLocalRef(mDvrObj));
if (!env->IsSameObject(dvr, nullptr)) {
env->CallVoidMethod(dvr, gFields.onDvrRecordStatusID, (jint)status);
+ env->DeleteLocalRef(dvr);
} else {
ALOGE("DvrClientCallbackImpl::onRecordStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -416,6 +420,7 @@
jobject dvr(env->NewLocalRef(mDvrObj));
if (!env->IsSameObject(dvr, nullptr)) {
env->CallVoidMethod(dvr, gFields.onDvrPlaybackStatusID, (jint)status);
+ env->DeleteLocalRef(dvr);
} else {
ALOGE("DvrClientCallbackImpl::onPlaybackStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -603,6 +608,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
@@ -673,6 +679,10 @@
}
env->SetObjectArrayElement(arr, size, obj);
+ if(audioDescriptor != nullptr) {
+ env->DeleteLocalRef(audioDescriptor);
+ }
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
@@ -688,6 +698,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
@@ -725,6 +736,7 @@
jobject obj =
env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
@@ -745,6 +757,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
@@ -764,6 +777,7 @@
jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
itemFragmentIndex, lastItemFragmentIndex, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
@@ -776,6 +790,7 @@
jint dataLength = ipPayloadEvent.dataLength;
jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
@@ -794,6 +809,8 @@
jobject obj = env->NewObject(eventClazz, eventInit, pts, descrTag, array);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(array);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
@@ -807,6 +824,7 @@
.get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
@@ -819,6 +837,7 @@
.get<DemuxFilterMonitorEvent::Tag::cid>();
jobject obj = env->NewObject(eventClazz, eventInit, cid);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
@@ -830,6 +849,7 @@
const int32_t &startId = event.get<DemuxFilterEvent::Tag::startId>();
jobject obj = env->NewObject(eventClazz, eventInit, startId);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
@@ -922,10 +942,12 @@
methodID = gFields.onSharedFilterEventID;
}
env->CallVoidMethod(filter, methodID, array);
+ env->DeleteLocalRef(filter);
} else {
ALOGE("FilterClientCallbackImpl::onFilterEvent:"
"Filter object has been freed. Ignoring callback.");
}
+ env->DeleteLocalRef(array);
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -938,6 +960,7 @@
methodID = gFields.onSharedFilterStatusID;
}
env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
+ env->DeleteLocalRef(filter);
} else {
ALOGE("FilterClientCallbackImpl::onFilterStatus:"
"Filter object has been freed. Ignoring callback.");
@@ -1006,6 +1029,7 @@
frontend,
gFields.onFrontendEventID,
(jint)frontendEventType);
+ env->DeleteLocalRef(frontend);
} else {
ALOGW("FrontendClientCallbackImpl::onEvent:"
"Frontend object has been freed. Ignoring callback.");
@@ -1028,6 +1052,7 @@
continue;
}
executeOnScanMessage(env, clazz, frontend, type, message);
+ env->DeleteLocalRef(frontend);
}
}
@@ -1069,6 +1094,7 @@
env->SetLongArrayRegion(freqs, 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([J)V"),
freqs);
+ env->DeleteLocalRef(freqs);
break;
}
case FrontendScanMessageType::SYMBOL_RATE: {
@@ -1077,6 +1103,7 @@
env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
symbolRates);
+ env->DeleteLocalRef(symbolRates);
break;
}
case FrontendScanMessageType::HIERARCHY: {
@@ -1094,6 +1121,7 @@
jintArray plpIds = env->NewIntArray(jintV.size());
env->SetIntArrayRegion(plpIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds);
+ env->DeleteLocalRef(plpIds);
break;
}
case FrontendScanMessageType::GROUP_IDS: {
@@ -1101,6 +1129,7 @@
jintArray groupIds = env->NewIntArray(jintV.size());
env->SetIntArrayRegion(groupIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds);
+ env->DeleteLocalRef(groupIds);
break;
}
case FrontendScanMessageType::INPUT_STREAM_IDS: {
@@ -1109,6 +1138,7 @@
env->SetIntArrayRegion(streamIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
streamIds);
+ env->DeleteLocalRef(streamIds);
break;
}
case FrontendScanMessageType::STANDARD: {
@@ -1142,12 +1172,14 @@
jboolean lls = info.bLlsFlag;
jobject obj = env->NewObject(plpClazz, init, plpId, lls);
env->SetObjectArrayElement(array, i, obj);
+ env->DeleteLocalRef(obj);
}
env->CallVoidMethod(frontend,
env->GetMethodID(clazz, "onAtsc3PlpInfos",
"([Landroid/media/tv/tuner/frontend/"
"Atsc3PlpInfo;)V"),
array);
+ env->DeleteLocalRef(array);
break;
}
case FrontendScanMessageType::MODULATION: {
@@ -1219,6 +1251,7 @@
env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
cellIds);
+ env->DeleteLocalRef(cellIds);
break;
}
default:
@@ -1673,6 +1706,7 @@
for (int i = 0; i < size; i++) {
jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
env->SetObjectArrayElement(valObj, i, readinessObj);
+ env->DeleteLocalRef(readinessObj);
}
return valObj;
}
@@ -2081,6 +2115,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isDemodLocked>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::snr: {
@@ -2088,6 +2123,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::ber: {
@@ -2095,6 +2131,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::per: {
@@ -2102,6 +2139,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::preBer: {
@@ -2109,6 +2147,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::signalQuality: {
@@ -2116,6 +2155,7 @@
jobject newIntegerObj = env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::signalQuality>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::signalStrength: {
@@ -2124,6 +2164,7 @@
env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::signalStrength>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::symbolRate: {
@@ -2131,6 +2172,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::symbolRate>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::innerFec: {
@@ -2141,6 +2183,7 @@
env->NewObject(longClazz, initLong,
static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(newLongObj);
break;
}
case FrontendStatus::Tag::modulationStatus: {
@@ -2183,6 +2226,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2192,6 +2236,7 @@
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::inversion>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::lnbVoltage: {
@@ -2200,6 +2245,7 @@
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::plpId: {
@@ -2207,6 +2253,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isEWBS: {
@@ -2214,6 +2261,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isEWBS>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::agc: {
@@ -2221,6 +2269,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isLnaOn: {
@@ -2228,6 +2277,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isLnaOn>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isLayerError: {
@@ -2241,6 +2291,7 @@
env->SetBooleanArrayRegion(valObj, i, 1, &x);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::mer: {
@@ -2248,6 +2299,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::freqOffset: {
@@ -2255,6 +2307,7 @@
jobject newLongObj = env->NewObject(longClazz, initLong,
s.get<FrontendStatus::Tag::freqOffset>());
env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(newLongObj);
break;
}
case FrontendStatus::Tag::hierarchy: {
@@ -2263,6 +2316,7 @@
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isRfLocked: {
@@ -2270,6 +2324,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isRfLocked>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::plpInfo: {
@@ -2289,9 +2344,11 @@
jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
env->SetObjectArrayElement(valObj, i, plpObj);
+ env->DeleteLocalRef(plpObj);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::modulations: {
@@ -2374,6 +2431,7 @@
if (valid) {
env->SetObjectField(statusObj, field, valObj);
}
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::bers: {
@@ -2384,6 +2442,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::codeRates: {
@@ -2394,6 +2453,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::bandwidth: {
@@ -2434,6 +2494,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intBandwidth);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2465,6 +2526,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intInterval);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2497,6 +2559,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intTransmissionMode);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2505,6 +2568,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::systemId: {
@@ -2512,6 +2576,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::interleaving: {
@@ -2558,6 +2623,7 @@
if (valid) {
env->SetObjectField(statusObj, field, valObj);
}
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::isdbtSegment: {
@@ -2568,6 +2634,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::tsDataRate: {
@@ -2578,6 +2645,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::rollOff: {
@@ -2605,6 +2673,7 @@
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intRollOff);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2613,6 +2682,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isMiso>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isLinear: {
@@ -2620,6 +2690,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isLinear>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isShortFrames: {
@@ -2627,6 +2698,7 @@
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isShortFrames>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isdbtMode: {
@@ -2634,6 +2706,7 @@
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::isdbtMode>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::partialReceptionFlag: {
@@ -2643,6 +2716,7 @@
env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::partialReceptionFlag>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::streamIdList: {
@@ -2653,6 +2727,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::dvbtCellIds: {
@@ -2663,6 +2738,7 @@
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::allPlpInfo: {
@@ -2678,9 +2754,11 @@
jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
plpInfos[i].bLlsFlag);
env->SetObjectArrayElement(valObj, i, plpObj);
+ env->DeleteLocalRef(plpObj);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
}
@@ -2837,6 +2915,7 @@
.fec = fec,
};
plps[i] = frontendAtsc3PlpSettings;
+ env->DeleteLocalRef(plp);
}
return plps;
}
@@ -3192,6 +3271,7 @@
env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
frontendIsdbtSettings.layerSettings[i].numOfSegment =
env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
+ env->DeleteLocalRef(layer);
}
frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
diff --git a/media/mca/effect/java/android/media/effect/EffectFactory.java b/media/mca/effect/java/android/media/effect/EffectFactory.java
index f6fcba7..cbb2736 100644
--- a/media/mca/effect/java/android/media/effect/EffectFactory.java
+++ b/media/mca/effect/java/android/media/effect/EffectFactory.java
@@ -486,11 +486,9 @@
private Effect instantiateEffect(Class effectClass, String name) {
// Make sure this is an Effect subclass
- try {
- effectClass.asSubclass(Effect.class);
- } catch (ClassCastException e) {
+ if (!Effect.class.isAssignableFrom(effectClass)) {
throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass
- + "' which is not a subclass of Effect!", e);
+ + "' which is not a subclass of Effect!");
}
// Look for the correct constructor
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index a608ef5..e82c046 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -90,9 +90,7 @@
return false;
}
// Then make sure it's a subclass of Filter.
- try {
- filterClass.asSubclass(Filter.class);
- } catch (ClassCastException e) {
+ if (!Filter.class.isAssignableFrom(filterClass)) {
return false;
}
return true;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
index 779df99..736e511 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
@@ -112,9 +112,7 @@
public Filter createFilterByClass(Class filterClass, String filterName) {
// Make sure this is a Filter subclass
- try {
- filterClass.asSubclass(Filter.class);
- } catch (ClassCastException e) {
+ if (!Filter.class.isAssignableFrom(filterClass)) {
throw new IllegalArgumentException("Attempting to allocate class '" + filterClass
+ "' which is not a subclass of Filter!");
}
diff --git a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
index 8cf9a13..6ff1885 100644
--- a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
+++ b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
@@ -55,12 +55,12 @@
public int getInt(String key) {
Object result = get(key);
- return result != null ? (Integer)result : null;
+ return result != null ? (Integer) result : 0;
}
public float getFloat(String key) {
Object result = get(key);
- return result != null ? (Float)result : null;
+ return result != null ? (Float) result : 0;
}
@Override
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index c5281657..8c05725 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -296,7 +296,7 @@
mMemWriter.write("End Memory :" + mEndMemory + "\n");
}
} catch (Exception e) {
- e.toString();
+ // TODO
}
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index 39add7e..c814eba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -264,8 +264,14 @@
builder.append("**");
}
- if (elem instanceof Number) {
- builder.append(String.format("%x", elem));
+ if (elem instanceof Byte) {
+ builder.append(String.format("%x", (Byte) elem));
+ } else if (elem instanceof Short) {
+ builder.append(String.format("%x", (Short) elem));
+ } else if (elem instanceof Integer) {
+ builder.append(String.format("%x", (Integer) elem));
+ } else if (elem instanceof Long) {
+ builder.append(String.format("%x", (Long) elem));
} else {
builder.append(elem);
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
index fd1c2d3..37dd4b5 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
@@ -18,7 +18,7 @@
import android.media.MediaPlayer;
import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;;
+import android.test.suitebuilder.annotation.LargeTest;
/**
* Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 810b408..4193ffa 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -384,7 +384,7 @@
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertThat(routeToSelect).isNotNull();
- mManager.selectRoute(mPackageName, routeToSelect);
+ mManager.transfer(mPackageName, routeToSelect);
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(mManager.getRemoteSessions()).hasSize(1);
}
@@ -410,7 +410,7 @@
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
- mManager.selectRoute(mPackageName, routeToSelect);
+ mManager.transfer(mPackageName, routeToSelect);
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -514,7 +514,7 @@
}
});
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
+ () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -525,7 +525,7 @@
RoutingSessionInfo sessionInfo = sessions.get(1);
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+ () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
ROUTE_ID5_TO_TRANSFER_TO,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
@@ -583,9 +583,9 @@
assertThat(route1).isNotNull();
assertThat(route2).isNotNull();
- mManager.selectRoute(mPackageName, route1);
+ mManager.transfer(mPackageName, route1);
assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
- mManager.selectRoute(mPackageName, route2);
+ mManager.transfer(mPackageName, route2);
assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
// onTransferFailed/onSessionReleased should not be called.
@@ -703,7 +703,7 @@
}
});
- mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
+ mManager.transfer(mPackageName, routes.get(ROUTE_ID1));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -858,7 +858,7 @@
});
mRouter2.setOnGetControllerHintsListener(listener);
- mManager.selectRoute(mPackageName, route);
+ mManager.transfer(mPackageName, route);
assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -903,7 +903,7 @@
}
});
- mManager.selectRoute(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index cb0f22f..9b0f020 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -238,7 +238,7 @@
ASurfaceControl_createFromWindow; # introduced=29
ASurfaceControl_acquire; # introduced=31
ASurfaceControl_release; # introduced=29
- ASurfaceControl_fromSurfaceControl; # introduced=34
+ ASurfaceControl_fromJava; # introduced=34
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -256,7 +256,7 @@
ASurfaceTransaction_apply; # introduced=29
ASurfaceTransaction_create; # introduced=29
ASurfaceTransaction_delete; # introduced=29
- ASurfaceTransaction_fromTransaction; # introduced=34
+ ASurfaceTransaction_fromJava; # introduced=34
ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
ASurfaceTransaction_setBufferAlpha; # introduced=29
@@ -330,6 +330,7 @@
APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
APerformanceHint_closeSession; # introduced=Tiramisu
+ APerformanceHint_sendHint; # introduced=UpsideDownCake
local:
*;
};
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index d627984..7863a7d 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -61,6 +61,7 @@
int updateTargetWorkDuration(int64_t targetDurationNanos);
int reportActualWorkDuration(int64_t actualDurationNanos);
+ int sendHint(int32_t hint);
private:
friend struct APerformanceHintManager;
@@ -159,7 +160,7 @@
}
binder::Status ret = mHintSession->updateTargetWorkDuration(targetDurationNanos);
if (!ret.isOk()) {
- ALOGE("%s: HintSessionn updateTargetWorkDuration failed: %s", __FUNCTION__,
+ ALOGE("%s: HintSession updateTargetWorkDuration failed: %s", __FUNCTION__,
ret.exceptionMessage().c_str());
return EPIPE;
}
@@ -205,6 +206,21 @@
return 0;
}
+int APerformanceHintSession::sendHint(int32_t hint) {
+ if (hint < 0) {
+ ALOGE("%s: session hint value must be greater than zero", __FUNCTION__);
+ return EINVAL;
+ }
+
+ binder::Status ret = mHintSession->sendHint(hint);
+
+ if (!ret.isOk()) {
+ ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+ return EPIPE;
+ }
+ return 0;
+}
+
// ===================================== C API
APerformanceHintManager* APerformanceHint_getManager() {
return APerformanceHintManager::getInstance();
@@ -230,6 +246,10 @@
return session->reportActualWorkDuration(actualDurationNanos);
}
+int APerformanceHint_sendHint(APerformanceHintSession* session, int32_t hint) {
+ return session->sendHint(hint);
+}
+
void APerformanceHint_closeSession(APerformanceHintSession* session) {
delete session;
}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 8913799..ea20c6c 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -138,17 +138,14 @@
SurfaceControl_release(surfaceControl);
}
-ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
- LOG_ALWAYS_FATAL_IF(!env,
- "nullptr passed to ASurfaceControl_fromSurfaceControl as env argument");
+ASurfaceControl* ASurfaceControl_fromJava(JNIEnv* env, jobject surfaceControlObj) {
+ LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceControl_fromJava as env argument");
LOG_ALWAYS_FATAL_IF(!surfaceControlObj,
- "nullptr passed to ASurfaceControl_fromSurfaceControl as surfaceControlObj "
- "argument");
+ "nullptr passed to ASurfaceControl_fromJava as surfaceControlObj argument");
SurfaceControl* surfaceControl =
android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj);
LOG_ALWAYS_FATAL_IF(!surfaceControl,
- "surfaceControlObj passed to ASurfaceControl_fromSurfaceControl is not "
- "valid");
+ "surfaceControlObj passed to ASurfaceControl_fromJava is not valid");
SurfaceControl_acquire(surfaceControl);
return reinterpret_cast<ASurfaceControl*>(surfaceControl);
}
@@ -209,17 +206,15 @@
delete transaction;
}
-ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
- LOG_ALWAYS_FATAL_IF(!env,
- "nullptr passed to ASurfaceTransaction_fromTransaction as env argument");
+ASurfaceTransaction* ASurfaceTransaction_fromJava(JNIEnv* env, jobject transactionObj) {
+ LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceTransaction_fromJava as env argument");
LOG_ALWAYS_FATAL_IF(!transactionObj,
- "nullptr passed to ASurfaceTransaction_fromTransaction as transactionObj "
+ "nullptr passed to ASurfaceTransaction_fromJava as transactionObj "
"argument");
Transaction* transaction =
android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj);
LOG_ALWAYS_FATAL_IF(!transaction,
- "surfaceControlObj passed to ASurfaceTransaction_fromTransaction is not "
- "valid");
+ "surfaceControlObj passed to ASurfaceTransaction_fromJava is not valid");
return reinterpret_cast<ASurfaceTransaction*>(transaction);
}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index b17850e..1881e60 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -51,6 +51,7 @@
(const ::std::vector<int64_t>& actualDurationNanos,
const ::std::vector<int64_t>& timeStampNanos),
(override));
+ MOCK_METHOD(Status, sendHint, (int32_t hints), (override));
MOCK_METHOD(Status, close, (), (override));
MOCK_METHOD(IBinder*, onAsBinder, (), (override));
};
@@ -121,6 +122,15 @@
result = APerformanceHint_reportActualWorkDuration(session, -1L);
EXPECT_EQ(EINVAL, result);
+ // Send both valid and invalid session hints
+ int hintId = 2;
+ EXPECT_CALL(*iSession, sendHint(Eq(2))).Times(Exactly(1));
+ result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_EQ(0, result);
+
+ result = APerformanceHint_sendHint(session, -1);
+ EXPECT_EQ(EINVAL, result);
+
EXPECT_CALL(*iSession, close()).Times(Exactly(1));
APerformanceHint_closeSession(session);
}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 1709dfd..10c570b 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -93,7 +93,7 @@
],
static_libs: ["libarect"],
fuzz_config: {
- cc: ["scroggo@google.com"],
+ cc: ["dichenzhang@google.com","scroggo@google.com"],
asan_options: [
"detect_odr_violation=1",
],
diff --git a/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml b/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml
index d096d98..d5019d5 100644
--- a/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="backup_confirm_title" msgid="827563724209303345">"Full backup"</string>
- <string name="restore_confirm_title" msgid="5469365809567486602">"Full restoration"</string>
+ <string name="restore_confirm_title" msgid="5469365809567486602">"Full restore"</string>
<string name="backup_confirm_text" msgid="1878021282758896593">"A full backup of all data to a connected desktop computer has been requested. Do you want to allow this to happen?\n\nIf you did not request the backup yourself, do not allow the operation to proceed."</string>
<string name="allow_backup_button_label" msgid="4217228747769644068">"Back up my data"</string>
<string name="deny_backup_button_label" msgid="6009119115581097708">"Do not back up"</string>
@@ -32,7 +32,7 @@
<string name="restore_enc_password_text" msgid="6140898525580710823">"If the restore data is encrypted, please enter the password below:"</string>
<string name="toast_backup_started" msgid="550354281452756121">"Backup starting..."</string>
<string name="toast_backup_ended" msgid="3818080769548726424">"Backup finished"</string>
- <string name="toast_restore_started" msgid="7881679218971277385">"Restoration starting..."</string>
- <string name="toast_restore_ended" msgid="1764041639199696132">"Restoration ended"</string>
+ <string name="toast_restore_started" msgid="7881679218971277385">"Restore starting..."</string>
+ <string name="toast_restore_ended" msgid="1764041639199696132">"Restore ended"</string>
<string name="toast_timeout" msgid="5276598587087626877">"Operation timed out"</string>
</resources>
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 4c22ee6..c4bb17c 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,7 +34,8 @@
android:label="@string/app_name"
android:directBootAware="true"
android:usesCleartextTraffic="true"
- android:icon="@mipmap/ic_launcher_android">
+ android:icon="@mipmap/ic_launcher_android"
+ android:debuggable="true">
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index 8a19709..3dcdf00 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -15,7 +15,7 @@
<string name="ssl_error_continue">Continue anyway via browser</string>
<!-- Telephony notification channel name for network boost notifications. -->
- <string name="network_boost_notification_channel">Network Boost</string>
+ <string name="network_boost_notification_channel">Network boost</string>
<!-- Notification title text for the network boost notification. -->
<string name="network_boost_notification_title">%s recommends a data boost</string>
<!-- Notification detail text for the network boost notification. -->
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index e67ea7e..c524037 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -28,6 +28,7 @@
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
+import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
@@ -58,38 +59,40 @@
public class SlicePurchaseActivity extends Activity {
private static final String TAG = "SlicePurchaseActivity";
- private @NonNull WebView mWebView;
- private @NonNull Context mApplicationContext;
+ @NonNull private WebView mWebView;
+ @NonNull private Context mApplicationContext;
+ @NonNull private Intent mIntent;
+ @Nullable private URL mUrl;
private int mSubId;
@TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Intent intent = getIntent();
- mSubId = intent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+ mIntent = getIntent();
+ mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- mCapability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+ mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
mApplicationContext = getApplicationContext();
- URL url = getUrl();
+ mUrl = getUrl();
logd("onCreate: subId=" + mSubId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + ", url=" + url);
+ + ", url=" + mUrl);
// Cancel network boost notification
mApplicationContext.getSystemService(NotificationManager.class)
.cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
// Verify intent and values are valid
- if (!SlicePurchaseBroadcastReceiver.isIntentValid(intent)) {
- loge("Not starting SlicePurchaseActivity with an invalid Intent: " + intent);
+ if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
+ loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
finishAndRemoveTask();
return;
}
- if (url == null) {
+ if (mUrl == null) {
String error = "Unable to create a URL from carrier configs.";
loge(error);
Intent data = new Intent();
@@ -97,7 +100,7 @@
SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
return;
}
@@ -105,7 +108,7 @@
loge("Unable to start the slice purchase application on the non-default data "
+ "subscription: " + mSubId);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
finishAndRemoveTask();
return;
}
@@ -114,16 +117,7 @@
SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
// Create and configure WebView
- mWebView = new WebView(this);
- // Enable JavaScript for the carrier purchase website to send results back to
- // the slice purchase application.
- mWebView.getSettings().setJavaScriptEnabled(true);
- mWebView.addJavascriptInterface(
- new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
-
- // Display WebView
- setContentView(mWebView);
- mWebView.loadUrl(url.toString());
+ setupWebView();
}
protected void onPurchaseSuccessful(long duration) {
@@ -134,7 +128,7 @@
Intent intent = new Intent();
intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
finishAndRemoveTask();
}
@@ -147,7 +141,7 @@
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode);
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, failureReason);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
}
@@ -166,7 +160,7 @@
protected void onDestroy() {
logd("onDestroy: User canceled the purchase by closing the application.");
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CANCELED);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
super.onDestroy();
}
@@ -175,14 +169,37 @@
String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mSubId).getString(
CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
- try {
- return new URL(url);
- } catch (MalformedURLException e) {
- loge("Invalid URL: " + url);
+ boolean isUrlValid = URLUtil.isValidUrl(url);
+ if (URLUtil.isAssetUrl(url)) {
+ isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
}
+ if (isUrlValid) {
+ try {
+ return new URL(url);
+ } catch (MalformedURLException ignored) {
+ }
+ }
+ loge("Invalid URL: " + url);
return null;
}
+ private void setupWebView() {
+ // Create WebView
+ mWebView = new WebView(this);
+
+ // Enable JavaScript for the carrier purchase website to send results back to
+ // the slice purchase application.
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.addJavascriptInterface(
+ new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+
+ // Display WebView
+ setContentView(mWebView);
+
+ // Load the URL
+ mWebView.loadUrl(mUrl.toString());
+ }
+
private static void logd(@NonNull String s) {
Log.d(TAG, s);
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 5761b3c..b322b8b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -33,6 +33,7 @@
import android.util.Log;
import android.webkit.WebView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.slice.SlicePurchaseController;
import java.lang.ref.WeakReference;
@@ -173,7 +174,7 @@
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR)
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
&& isPendingIntentValid(intent,
- SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB)
+ SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
}
@@ -204,8 +205,8 @@
case SlicePurchaseController.EXTRA_INTENT_CANCELED: return "canceled";
case SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
- case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB:
- return "not default data sub";
+ case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+ return "not default data subscription";
case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
default: {
loge("Unknown pending intent extra: " + extra);
@@ -239,11 +240,15 @@
return;
}
- context.getSystemService(NotificationManager.class).createNotificationChannel(
- new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
- context.getResources().getString(
- R.string.network_boost_notification_channel),
- NotificationManager.IMPORTANCE_DEFAULT));
+ NotificationChannel channel = new NotificationChannel(
+ NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
+ context.getResources().getString(R.string.network_boost_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
+ // to allow users to disable notifications posted to this channel without affecting other
+ // notifications in this application.
+ channel.setBlockable(true);
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
Notification notification =
new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
@@ -291,7 +296,8 @@
*
* @return The intent to start {@link SlicePurchaseActivity}.
*/
- @NonNull private PendingIntent createContentIntent(@NonNull Context context,
+ @VisibleForTesting
+ @NonNull public PendingIntent createContentIntent(@NonNull Context context,
@NonNull Intent intent, int requestCode) {
Intent i = new Intent(context, SlicePurchaseActivity.class);
i.setComponent(ComponentName.unflattenFromString(
@@ -314,7 +320,8 @@
*
* @return The canceled intent.
*/
- @NonNull private PendingIntent createCanceledIntent(@NonNull Context context,
+ @VisibleForTesting
+ @NonNull public PendingIntent createCanceledIntent(@NonNull Context context,
@NonNull Intent intent) {
Intent i = new Intent(ACTION_NOTIFICATION_CANCELED);
i.setComponent(ComponentName.unflattenFromString(
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index 54c9016..cdf7957 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -27,11 +27,13 @@
libs: [
"android.test.runner",
"android.test.base",
+ "SlicePurchaseController",
],
static_libs: [
"androidx.test.rules",
- "mockito-target-minus-junit4",
+ "mockito-target-inline-minus-junit4",
],
+ jni_libs: ["libdexmakerjvmtiagent"],
// Include all test java files.
srcs: ["src/**/*.java"],
platform_apis: true,
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
new file mode 100644
index 0000000..cecc86d
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.carrierdefaultapp;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.test.ActivityUnitTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
+ private static final String TAG = "SlicePurchaseActivityTest";
+ private static final String URL = "file:///android_asset/slice_purchase_test.html";
+ private static final int PHONE_ID = 0;
+
+ @Mock PendingIntent mPendingIntent;
+ @Mock PendingIntent mCanceledIntent;
+ @Mock CarrierConfigManager mCarrierConfigManager;
+ @Mock NotificationManager mNotificationManager;
+ @Mock PersistableBundle mPersistableBundle;
+
+ private SlicePurchaseActivity mSlicePurchaseActivity;
+ private Context mContext;
+
+ public SlicePurchaseActivityTest() {
+ super(SlicePurchaseActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ // setup context
+ mContext = spy(getInstrumentation().getTargetContext());
+ doReturn(mCarrierConfigManager).when(mContext)
+ .getSystemService(eq(CarrierConfigManager.class));
+ doReturn(URL).when(mPersistableBundle).getString(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+ doReturn(mPersistableBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+ doReturn(mNotificationManager).when(mContext)
+ .getSystemService(eq(NotificationManager.class));
+ doReturn(mContext).when(mContext).getApplicationContext();
+ setActivityContext(mContext);
+
+ // set up intent
+ Intent intent = new Intent();
+ intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+ intent.putExtra(SlicePurchaseController.EXTRA_SUB_ID,
+ SubscriptionManager.getDefaultDataSubscriptionId());
+ intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
+ Intent spiedIntent = spy(intent);
+
+ // set up pending intents
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+ doReturn(true).when(mPendingIntent).isBroadcast();
+ doReturn(mPendingIntent).when(spiedIntent).getParcelableExtra(
+ anyString(), eq(PendingIntent.class));
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mCanceledIntent).getCreatorPackage();
+ doReturn(true).when(mCanceledIntent).isBroadcast();
+ doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
+ eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+
+ mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSlicePurchaseActivity.onDestroy();
+ super.tearDown();
+ }
+
+ @Test
+ public void testOnPurchaseSuccessful() throws Exception {
+ int duration = 5 * 60 * 1000; // 5 minutes
+ int invalidDuration = -1;
+ mSlicePurchaseActivity.onPurchaseSuccessful(duration);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+ Intent intent = intentCaptor.getValue();
+ assertEquals(duration, intent.getLongExtra(
+ SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+ }
+
+ @Test
+ public void testOnPurchaseFailed() throws Exception {
+ int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+ String failureReason = "Server unreachable";
+ mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+ Intent intent = intentCaptor.getValue();
+ assertEquals(failureCode, intent.getIntExtra(
+ SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode));
+ assertEquals(failureReason, intent.getStringExtra(
+ SlicePurchaseController.EXTRA_FAILURE_REASON));
+ }
+
+ @Test
+ public void testOnUserCanceled() throws Exception {
+ mSlicePurchaseActivity.onDestroy();
+ verify(mCanceledIntent).send();
+ }
+}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
new file mode 100644
index 0000000..5765e5b
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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.carrierdefaultapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.DisplayMetrics;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseBroadcastReceiverTest {
+ private static final int PHONE_ID = 0;
+ private static final String TAG = "SlicePurchaseBroadcastReceiverTest";
+ private static final String EXTRA = "EXTRA";
+
+ @Mock Intent mIntent;
+ @Mock Intent mDataIntent;
+ @Mock PendingIntent mPendingIntent;
+ @Mock PendingIntent mCanceledIntent;
+ @Mock PendingIntent mContentIntent1;
+ @Mock PendingIntent mContentIntent2;
+ @Mock Context mContext;
+ @Mock Resources mResources;
+ @Mock NotificationManager mNotificationManager;
+ @Mock ApplicationInfo mApplicationInfo;
+ @Mock PackageManager mPackageManager;
+ @Mock DisplayMetrics mDisplayMetrics;
+ @Mock SlicePurchaseActivity mSlicePurchaseActivity;
+
+ private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
+ private ArgumentCaptor<Intent> mIntentCaptor;
+ private ArgumentCaptor<Notification> mNotificationCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mNotificationManager).when(mContext)
+ .getSystemService(eq(NotificationManager.class));
+
+ mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ mNotificationCaptor = ArgumentCaptor.forClass(Notification.class);
+ mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
+ }
+
+ @Test
+ public void testSendSlicePurchaseAppResponse() throws Exception {
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+ verify(mPendingIntent, never()).send();
+
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ eq(EXTRA), eq(PendingIntent.class));
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+ verify(mPendingIntent).send();
+ }
+
+ @Test
+ public void testSendSlicePurchaseAppResponseWithData() throws Exception {
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+ mContext, mIntent, EXTRA, mDataIntent);
+ verify(mPendingIntent, never()).send(eq(mContext), eq(0), any(Intent.class));
+
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ eq(EXTRA), eq(PendingIntent.class));
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+ mContext, mIntent, EXTRA, mDataIntent);
+ verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture());
+ assertEquals(mDataIntent, mIntentCaptor.getValue());
+ }
+
+ @Test
+ public void testIsIntentValid() {
+ assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+ // set up intent
+ doReturn(PHONE_ID).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+ doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(TAG).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+ assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+ // set up pending intent
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+ doReturn(true).when(mPendingIntent).isBroadcast();
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ anyString(), eq(PendingIntent.class));
+ assertTrue(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+ }
+
+ @Test
+ public void testDisplayBoosterNotification() {
+ // set up intent
+ doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
+ doReturn(PHONE_ID).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+ doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(TAG).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+
+ // set up pending intent
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+ doReturn(true).when(mPendingIntent).isBroadcast();
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ anyString(), eq(PendingIntent.class));
+
+ // set up notification
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+ doReturn("").when(mResources).getString(anyInt());
+ doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+
+ // set up intents created by broadcast receiver
+ doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+ eq(mContext), eq(mIntent), eq(1));
+ doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+ eq(mContext), eq(mIntent), eq(2));
+ doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
+ eq(mContext), eq(mIntent));
+
+ // send ACTION_START_SLICE_PURCHASE_APP
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify network boost notification was shown
+ verify(mNotificationManager).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ mNotificationCaptor.capture(),
+ eq(UserHandle.ALL));
+
+ Notification notification = mNotificationCaptor.getValue();
+ assertEquals(mContentIntent1, notification.contentIntent);
+ assertEquals(mPendingIntent, notification.deleteIntent);
+ assertEquals(2, notification.actions.length);
+ assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
+ assertEquals(mContentIntent2, notification.actions[1].actionIntent);
+ }
+
+
+ @Test
+ public void testNotificationCanceled() {
+ // set up intent
+ doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+ // send ACTION_NOTIFICATION_CANCELED
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify notification was canceled
+ verify(mNotificationManager).cancelAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ eq(UserHandle.ALL));
+ }
+
+ @Test
+ public void testNotificationTimeout() {
+ // set up intent
+ doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+ .getAction();
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+ // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify notification was canceled
+ verify(mNotificationManager).cancelAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ eq(UserHandle.ALL));
+ }
+
+ @Test
+ // TODO: WebView/Activity should not close on timeout.
+ // This test should be removed once implementation is fixed.
+ public void testActivityTimeout() {
+ // create and track activity
+ SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity);
+
+ // set up intent
+ doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+ .getAction();
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+ // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify activity was canceled
+ verify(mSlicePurchaseActivity).finishAndRemoveTask();
+
+ // untrack activity
+ SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ }
+}
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index c80620e..3982809 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -20,7 +20,7 @@
<string name="confirmation_title" msgid="3785000297483688997">"Allow <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to access your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
- <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+ <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
<string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
<string name="permission_apps_summary" msgid="798718816711515431">"Stream your phone’s apps"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Allow <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to access this information from your phone"</string>
@@ -31,18 +31,18 @@
<string name="title_computer" msgid="4693714143506569253">"Allow <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to access this information from your phone"</string>
<string name="summary_computer" msgid="3798467601598297062"></string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
+ <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages, and photos"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
<string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
- <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
+ <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media, and notifications"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
- <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Don’t allow"</string>
<string name="consent_back" msgid="2560683030046918882">"Back"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> the same permissions as on <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
- <string name="permission_sync_summary" msgid="4866838188678457084">"<p>This may include microphone, camera and location access, and other sensitive permissions on <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>You can change these permissions at any time in your settings on <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string>
- <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
- <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+ <string name="permission_sync_summary" msgid="4866838188678457084">"<p>This may include Microphone, Camera, and Location access, and other sensitive permissions on <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>You can change these permissions any time in your Settings on <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string>
+ <string name="vendor_icon_description" msgid="4445875290032225965">"App Icon"</string>
+ <string name="vendor_header_button_description" msgid="6566660389500630608">"More Information Button"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index a7e1a59..723c59a 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -22,6 +22,7 @@
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.companion.CompanionDeviceManager.REASON_CANCELED;
import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
@@ -203,6 +204,7 @@
initUI();
}
+ @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
protected void onNewIntent(Intent intent) {
// Force cancels the CDM dialog if this activity receives another intent with
@@ -213,7 +215,8 @@
Log.i(TAG, "Cancelling the user confirmation");
- cancel(false, false);
+ cancel(/* discoveryTimeOut */ false,
+ /* userRejected */ false, /* internalError */ false);
return;
}
@@ -240,7 +243,8 @@
// TODO: handle config changes without cancelling.
if (!isDone()) {
- cancel(/* discoveryTimeOut */ false, /* userRejected */ false); // will finish()
+ cancel(/* discoveryTimeOut */ false,
+ /* userRejected */ false, /* internalError */ false); // will finish()
}
}
@@ -325,7 +329,8 @@
private void onDiscoveryStateChanged(DiscoveryState newState) {
if (newState == FINISHED_TIMEOUT
&& CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
- cancel(/* discoveryTimeOut */ true, /* userRejected */ false);
+ cancel(/* discoveryTimeOut */ true,
+ /* userRejected */ false, /* internalError */ false);
}
}
@@ -363,12 +368,14 @@
mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
}
- private void cancel(boolean discoveryTimeout, boolean userRejected) {
+ private void cancel(boolean discoveryTimeout, boolean userRejected, boolean internalError) {
if (DEBUG) {
Log.i(TAG, "cancel(), discoveryTimeout="
+ discoveryTimeout
+ ", userRejected="
- + userRejected, new Exception("Stack Trace Dump"));
+ + userRejected
+ + ", internalError="
+ + internalError, new Exception("Stack Trace Dump"));
}
if (isDone()) {
@@ -390,9 +397,12 @@
} else if (discoveryTimeout) {
cancelReason = REASON_DISCOVERY_TIMEOUT;
resultCode = RESULT_DISCOVERY_TIMEOUT;
+ } else if (internalError) {
+ cancelReason = REASON_INTERNAL_ERROR;
+ resultCode = RESULT_INTERNAL_ERROR;
} else {
cancelReason = REASON_CANCELED;
- resultCode = RESULT_CANCELED;
+ resultCode = CompanionDeviceManager.RESULT_CANCELED;
}
// First send callback to the app directly...
@@ -448,7 +458,8 @@
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
- setResultAndFinish(null, RESULT_INTERNAL_ERROR);
+ cancel(/* discoveryTimeout */ false,
+ /* userRejected */ false, /* internalError */ true);
return;
}
@@ -626,7 +637,7 @@
// Disable the button, to prevent more clicks.
v.setEnabled(false);
- cancel(/* discoveryTimeout */ false, /* userRejected */ true);
+ cancel(/* discoveryTimeout */ false, /* userRejected */ true, /* internalError */ false);
}
private void onShowHelperDialog(View view) {
@@ -651,7 +662,7 @@
final AssociationInfo association = data.getParcelable(
EXTRA_ASSOCIATION, AssociationInfo.class);
requireNonNull(association);
- setResultAndFinish(association, RESULT_OK);
+ setResultAndFinish(association, CompanionDeviceManager.RESULT_OK);
} else {
setResultAndFinish(null, resultCode);
}
@@ -660,7 +671,7 @@
@Override
public void onShowHelperDialogFailed() {
- setResultAndFinish(null, RESULT_INTERNAL_ERROR);
+ cancel(/* discoveryTimeout */ false, /* userRejected */ false, /* internalError */ true);
}
@Override
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 586ef86..bd27dab 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -36,8 +36,6 @@
android:name=".CredentialSelectorActivity"
android:exported="true"
android:label="@string/app_name"
- android:launchMode="singleInstance"
- android:noHistory="true"
android:excludeFromRecents="true"
android:theme="@style/Theme.CredentialSelector">
</activity>
diff --git a/packages/CredentialManager/res/drawable/ic_face.xml b/packages/CredentialManager/res/drawable/ic_face.xml
new file mode 100644
index 0000000..16fe144
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_face.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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.
+ -->
+
+<!--TODO: Testing only icon. Remove later. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#808080"
+ android:pathData="M9.025,14.275Q8.5,14.275 8.125,13.9Q7.75,13.525 7.75,13Q7.75,12.475 8.125,12.1Q8.5,11.725 9.025,11.725Q9.575,11.725 9.938,12.1Q10.3,12.475 10.3,13Q10.3,13.525 9.938,13.9Q9.575,14.275 9.025,14.275ZM14.975,14.275Q14.425,14.275 14.062,13.9Q13.7,13.525 13.7,13Q13.7,12.475 14.062,12.1Q14.425,11.725 14.975,11.725Q15.5,11.725 15.875,12.1Q16.25,12.475 16.25,13Q16.25,13.525 15.875,13.9Q15.5,14.275 14.975,14.275ZM12,19.925Q15.325,19.925 17.625,17.625Q19.925,15.325 19.925,12Q19.925,11.4 19.85,10.85Q19.775,10.3 19.575,9.775Q19.05,9.9 18.538,9.962Q18.025,10.025 17.45,10.025Q15.2,10.025 13.188,9.062Q11.175,8.1 9.775,6.375Q8.975,8.3 7.5,9.712Q6.025,11.125 4.075,11.85Q4.075,11.9 4.075,11.925Q4.075,11.95 4.075,12Q4.075,15.325 6.375,17.625Q8.675,19.925 12,19.925ZM12,22.2Q9.9,22.2 8.038,21.4Q6.175,20.6 4.788,19.225Q3.4,17.85 2.6,15.988Q1.8,14.125 1.8,12Q1.8,9.875 2.6,8.012Q3.4,6.15 4.788,4.775Q6.175,3.4 8.038,2.6Q9.9,1.8 12,1.8Q14.125,1.8 15.988,2.6Q17.85,3.4 19.225,4.775Q20.6,6.15 21.4,8.012Q22.2,9.875 22.2,12Q22.2,14.125 21.4,15.988Q20.6,17.85 19.225,19.225Q17.85,20.6 15.988,21.4Q14.125,22.2 12,22.2Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_manage_accounts.xml b/packages/CredentialManager/res/drawable/ic_manage_accounts.xml
new file mode 100644
index 0000000..adad2f1
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_manage_accounts.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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.
+ -->
+
+<!--TODO: Testing only icon. Remove later. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#808080"
+ android:pathData="M16.1,21.2 L15.775,19.675Q15.5,19.55 15.25,19.425Q15,19.3 14.75,19.1L13.275,19.575L12.2,17.75L13.375,16.725Q13.325,16.4 13.325,16.112Q13.325,15.825 13.375,15.5L12.2,14.475L13.275,12.65L14.75,13.1Q15,12.925 15.25,12.787Q15.5,12.65 15.775,12.55L16.1,11.025H18.25L18.55,12.55Q18.825,12.65 19.075,12.8Q19.325,12.95 19.575,13.15L21.05,12.65L22.125,14.525L20.95,15.55Q21.025,15.825 21.013,16.137Q21,16.45 20.95,16.725L22.125,17.75L21.05,19.575L19.575,19.1Q19.325,19.3 19.075,19.425Q18.825,19.55 18.55,19.675L18.25,21.2ZM1.8,20.3V17.3Q1.8,16.375 2.275,15.613Q2.75,14.85 3.5,14.475Q4.775,13.825 6.425,13.362Q8.075,12.9 10,12.9Q10.2,12.9 10.4,12.9Q10.6,12.9 10.775,12.95Q9.925,14.85 10.062,16.738Q10.2,18.625 11.4,20.3ZM17.175,18.075Q17.975,18.075 18.55,17.487Q19.125,16.9 19.125,16.1Q19.125,15.3 18.55,14.725Q17.975,14.15 17.175,14.15Q16.375,14.15 15.788,14.725Q15.2,15.3 15.2,16.1Q15.2,16.9 15.788,17.487Q16.375,18.075 17.175,18.075ZM10,11.9Q8.25,11.9 7.025,10.662Q5.8,9.425 5.8,7.7Q5.8,5.95 7.025,4.725Q8.25,3.5 10,3.5Q11.75,3.5 12.975,4.725Q14.2,5.95 14.2,7.7Q14.2,9.425 12.975,10.662Q11.75,11.9 10,11.9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_other_devices.xml b/packages/CredentialManager/res/drawable/ic_other_devices.xml
new file mode 100644
index 0000000..754648c
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_other_devices.xml
@@ -0,0 +1,15 @@
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:name="path"
+ android:pathData="M 7.6 4.72 L 7.6 7.6 L 4.72 7.6 L 4.72 4.72 L 7.6 4.72 Z M 9.04 3.28 L 3.28 3.28 L 3.28 9.04 L 9.04 9.04 L 9.04 3.28 Z M 7.6 12.4 L 7.6 15.28 L 4.72 15.28 L 4.72 12.4 L 7.6 12.4 Z M 9.04 10.96 L 3.28 10.96 L 3.28 16.72 L 9.04 16.72 L 9.04 10.96 Z M 15.28 4.72 L 15.28 7.6 L 12.4 7.6 L 12.4 4.72 L 15.28 4.72 Z M 16.72 3.28 L 10.96 3.28 L 10.96 9.04 L 16.72 9.04 L 16.72 3.28 Z M 10.96 10.96 L 12.4 10.96 L 12.4 12.4 L 10.96 12.4 L 10.96 10.96 Z M 12.4 12.4 L 13.84 12.4 L 13.84 13.84 L 12.4 13.84 L 12.4 12.4 Z M 13.84 10.96 L 15.28 10.96 L 15.28 12.4 L 13.84 12.4 L 13.84 10.96 Z M 10.96 13.84 L 12.4 13.84 L 12.4 15.28 L 10.96 15.28 L 10.96 13.84 Z M 12.4 15.28 L 13.84 15.28 L 13.84 16.72 L 12.4 16.72 L 12.4 15.28 Z M 13.84 13.84 L 15.28 13.84 L 15.28 15.28 L 13.84 15.28 L 13.84 13.84 Z M 15.28 12.4 L 16.72 12.4 L 16.72 13.84 L 15.28 13.84 L 15.28 12.4 Z M 15.28 15.28 L 16.72 15.28 L 16.72 16.72 L 15.28 16.72 L 15.28 15.28 Z M 19.6 5.2 L 17.68 5.2 L 17.68 2.32 L 14.8 2.32 L 14.8 0.4 L 19.6 0.4 L 19.6 5.2 Z M 19.6 19.6 L 19.6 14.8 L 17.68 14.8 L 17.68 17.68 L 14.8 17.68 L 14.8 19.6 L 19.6 19.6 Z M 0.4 19.6 L 5.2 19.6 L 5.2 17.68 L 2.32 17.68 L 2.32 14.8 L 0.4 14.8 L 0.4 19.6 Z M 0.4 0.4 L 0.4 5.2 L 2.32 5.2 L 2.32 2.32 L 5.2 2.32 L 5.2 0.4 L 0.4 0.4 Z"
+ android:fillColor="#000000"
+ android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 6178efc..114de89 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -15,18 +15,55 @@
<string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
<string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
<string name="choose_sign_in_title">Use saved sign in</string>
- <string name="create_passkey_at">Create passkey at</string>
+ <string name="create_passkey_in">Create passkey in</string>
+ <string name="save_password_to">Save password to</string>
+ <string name="save_sign_in_to">Save sign-in to</string>
<string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string>
<string name="set_as_default">Set as default</string>
<string name="use_once">Use once</string>
- <string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
- <string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
- <string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
- <string name="more_options_usage_data"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords and <xliff:g id="passkeyssNumber">%2$s</xliff:g> passkeys saved</string>
- <string name="passkeys">passkeys</string>
- <string name="passwords">passwords</string>
+ <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName">%1$s</xliff:g> <xliff:g id="type">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName">%4$s</xliff:g></string>
+ <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber">%2$s</xliff:g> passkeys</string>
+ <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords</string>
+ <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber">%1$s</xliff:g> passkeys</string>
+ <string name="passkey">passkey</string>
+ <string name="password">password</string>
<string name="sign_ins">sign-ins</string>
- <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
+ <string name="another_device">Another device</string>
+ <string name="other_password_manager">Other password manager</string>
+ <!-- TODO: Check the wording here. -->
+ <string name="confirm_default_or_use_once_description">This password manager will store your passwords and passkeys to help you easily sign in.</string>
<!-- Spoken content description of an element which will close the sheet when clicked. -->
<string name="close_sheet">"Close sheet"</string>
+ <!-- Spoken content description of the back arrow button. -->
+ <string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
+
+ <!-- Strings for the get flow. -->
+ <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
+ <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <!-- This appears as the title of the dialog asking for user confirmation to use the single previously saved credential to sign in to the app. [CHAR LIMIT=200] -->
+ <string name="get_dialog_title_use_sign_in_for">Use your saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+ <string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+ <!-- Appears as an option row for viewing all the available sign-in options. [CHAR LIMIT=80] -->
+ <string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
+ <!-- Button label to close the dialog when the user does not want to use any sign-in. [CHAR LIMIT=40] -->
+ <string name="get_dialog_button_label_no_thanks">No thanks</string>
+ <!-- Button label to continue with the selected sign-in. [CHAR LIMIT=40] -->
+ <string name="get_dialog_button_label_continue">Continue</string>
+ <!-- Separator for sign-in type and username in a sign-in entry. -->
+ <string name="get_dialog_sign_in_type_username_separator" translatable="false">" - "</string>
+ <!-- Modal bottom sheet title for displaying all the available sign-in options. [CHAR LIMIT=80] -->
+ <string name="get_dialog_title_sign_in_options">Sign-in options</string>
+ <!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] -->
+ <string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string>
+ <!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] -->
+ <string name="get_dialog_heading_locked_password_managers">Locked password managers</string>
+ <!-- Explanatory sub/body text for an option entry to use a locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-in. [CHAR LIMIT=120] -->
+ <string name="locked_credential_entry_label_subtext">Tap to unlock</string>
+ <!-- Column heading for displaying action chips for managing sign-ins from each credential provider. [CHAR LIMIT=80] -->
+ <string name="get_dialog_heading_manage_sign_ins">Manage sign-ins</string>
+ <!-- Column heading for displaying option to use sign-ins saved on a different device. [CHAR LIMIT=80] -->
+ <string name="get_dialog_heading_from_another_device">From another device</string>
+ <!-- Headline text for an option to use sign-ins saved on a different device. [CHAR LIMIT=120] -->
+ <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
index feec746..a58a038 100644
--- a/packages/CredentialManager/res/values/themes.xml
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -2,11 +2,12 @@
<resources>
<style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
- <item name="android:statusBarColor">@color/purple_700</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="fontFamily">google-sans</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
deleted file mode 100644
index ee4f4ca..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
+++ /dev/null
@@ -1,59 +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.credentialmanager
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class CredentialEntryUi(
- val userName: CharSequence,
- val displayName: CharSequence?,
- val icon: Icon?,
- val usageData: CharSequence?,
- // TODO: add last used.
-) {
- companion object {
- fun fromSlice(slice: Slice): CredentialEntryUi {
- val items = slice.items
-
- var title: String? = null
- var subTitle: String? = null
- var icon: Icon? = null
- var usageData: String? = null
-
- items.forEach {
- if (it.hasHint(Entry.HINT_ICON)) {
- icon = it.icon
- } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
- subTitle = it.text.toString()
- } else if (it.hasHint(Entry.HINT_TITLE)) {
- title = it.text.toString()
- } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
- usageData = it.text.toString()
- }
- }
- // TODO: fail NPE more elegantly.
- return CredentialEntryUi(title!!, subTitle, icon, usageData)
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 9e9d16f..7e69987 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -16,15 +16,19 @@
package com.android.credentialmanager
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.content.Context
import android.content.Intent
import android.credentials.CreateCredentialRequest
+import android.credentials.GetCredentialOption
+import android.credentials.GetCredentialRequest
import android.credentials.ui.Constants
import android.credentials.ui.Entry
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.GetCredentialProviderData
+import android.credentials.ui.DisabledProviderData
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
import android.credentials.ui.BaseDialogResult
@@ -34,13 +38,16 @@
import android.os.Bundle
import android.os.ResultReceiver
import com.android.credentialmanager.createflow.ActiveEntry
-import com.android.credentialmanager.createflow.CreatePasskeyUiState
+import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
-import com.android.credentialmanager.createflow.ProviderInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
-import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest.Companion.createFrom
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
// Consider repo per screen, similar to view model?
class CredentialManagerRepo(
@@ -48,7 +55,8 @@
intent: Intent,
) {
private val requestInfo: RequestInfo
- private val providerList: List<ProviderData>
+ private val providerEnabledList: List<ProviderData>
+ private val providerDisabledList: List<DisabledProviderData>
// TODO: require non-null.
val resultReceiver: ResultReceiver?
@@ -56,18 +64,18 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testRequestInfo()
+ ) ?: testCreateRequestInfo()
- providerList = when (requestInfo.type) {
+ providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
CreateCredentialProviderData::class.java
- ) ?: testCreateCredentialProviderList()
+ ) ?: testCreateCredentialEnabledProviderList()
RequestInfo.TYPE_GET ->
intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
- GetCredentialProviderData::class.java
+ DisabledProviderData::class.java
) ?: testGetCredentialProviderList()
else -> {
// TODO: fail gracefully
@@ -75,6 +83,12 @@
}
}
+ providerDisabledList =
+ intent.extras?.getParcelableArrayList(
+ ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST,
+ DisabledProviderData::class.java
+ ) ?: testDisabledProviderList()
+
resultReceiver = intent.getParcelableExtra(
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
@@ -100,39 +114,48 @@
}
fun getCredentialInitialUiState(): GetCredentialUiState {
- val providerList = GetFlowUtils.toProviderList(
- // TODO: handle runtime cast error
- providerList as List<GetCredentialProviderData>, context)
+ val providerEnabledList = GetFlowUtils.toProviderList(
+ // TODO: handle runtime cast error
+ providerEnabledList as List<GetCredentialProviderData>, context)
// TODO: covert from real requestInfo
- val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo(
- "Elisa Beckett",
- "beckett-bakert@gmail.com",
- TYPE_PUBLIC_KEY_CREDENTIAL,
- "tribank")
+ val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("tribank")
return GetCredentialUiState(
- providerList,
- GetScreenState.CREDENTIAL_SELECTION,
+ providerEnabledList,
+ GetScreenState.PRIMARY_SELECTION,
requestDisplayInfo,
- providerList.first()
)
}
- fun createPasskeyInitialUiState(): CreatePasskeyUiState {
- val providerList = CreateFlowUtils.toProviderList(
+ fun createCredentialInitialUiState(): CreateCredentialUiState {
+ val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
// Handle runtime cast error
- providerList as List<CreateCredentialProviderData>, context)
+ providerEnabledList as List<CreateCredentialProviderData>, context)
+ val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
+ // Handle runtime cast error
+ providerDisabledList as List<DisabledProviderData>, context)
var hasDefault = false
- var defaultProvider: ProviderInfo = providerList.first()
- providerList.forEach{providerInfo ->
+ var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
+ providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
+ providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
- // TODO: covert from real requestInfo
- val requestDisplayInfo = RequestDisplayInfo(
+ // TODO: covert from real requestInfo for create passkey
+ var requestDisplayInfo = RequestDisplayInfo(
"Elisa Beckett",
"beckett-bakert@gmail.com",
TYPE_PUBLIC_KEY_CREDENTIAL,
"tribank")
- return CreatePasskeyUiState(
- providers = providerList,
+ val createCredentialRequest = requestInfo.createCredentialRequest
+ val createCredentialRequestJetpack = createCredentialRequest?.let { createFrom(it) }
+ if (createCredentialRequestJetpack is CreatePasswordRequest) {
+ requestDisplayInfo = RequestDisplayInfo(
+ createCredentialRequestJetpack.id,
+ createCredentialRequestJetpack.password,
+ TYPE_PASSWORD_CREDENTIAL,
+ "tribank")
+ }
+ return CreateCredentialUiState(
+ enabledProviders = providerEnabledList,
+ disabledProviders = providerDisabledList,
if (hasDefault)
{CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
requestDisplayInfo,
@@ -158,27 +181,30 @@
}
// TODO: below are prototype functionalities. To be removed for productionization.
- private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
+ private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
return listOf(
CreateCredentialProviderData
- .Builder("com.google/com.google.CredentialManagerService")
+ .Builder("io.enpass.app")
.setSaveEntries(
listOf<Entry>(
- newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+ newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
20, 7, 27, 10000),
- newEntry("key1", "subkey-2", "elisa.work@google.com",
+ newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
20, 7, 27, 11000),
)
)
+ .setRemoteEntry(
+ newRemoteEntry("key2", "subkey-1")
+ )
.setIsDefaultProvider(true)
.build(),
CreateCredentialProviderData
- .Builder("com.dashlane/com.dashlane.CredentialManagerService")
+ .Builder("com.dashlane")
.setSaveEntries(
listOf<Entry>(
- newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+ newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
20, 7, 27, 30000),
- newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+ newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
20, 7, 27, 31000),
)
)
@@ -186,42 +212,146 @@
)
}
+ private fun testDisabledProviderList(): List<DisabledProviderData> {
+ return listOf(
+ DisabledProviderData("com.lastpass.lpandroid"),
+ DisabledProviderData("com.google.android.youtube")
+ )
+ }
+
private fun testGetCredentialProviderList(): List<GetCredentialProviderData> {
return listOf(
- GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
+ GetCredentialProviderData.Builder("io.enpass.app")
.setCredentialEntries(
listOf<Entry>(
- newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
- 20, 7, 27, 10000),
- newEntry("key1", "subkey-2", "elisa.work@google.com",
- 20, 7, 27, 11000),
+ newGetEntry(
+ "key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+ "elisa.bakery@gmail.com", "Elisa Beckett", 300L
+ ),
+ newGetEntry(
+ "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
+ "elisa.bakery@gmail.com", null, 300L
+ ),
+ newGetEntry(
+ "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
+ "elisa.family@outlook.com", null, 100L
+ ),
)
+ ).setAuthenticationEntry(
+ newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL)
).setActionChips(
- listOf<Entry>(
- newEntry("key2", "subkey-1", "Go to Settings",
- 20, 7, 27, 20000),
- newEntry("key2", "subkey-2", "Switch Account",
- 20, 7, 27, 21000),
- ),
+ listOf(
+ newActionEntry(
+ "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
+ Icon.createWithResource(context, R.drawable.ic_manage_accounts),
+ "Open Google Password Manager", "elisa.beckett@gmail.com"
+ ),
+ newActionEntry(
+ "key3", "subkey-2", TYPE_PASSWORD_CREDENTIAL,
+ Icon.createWithResource(context, R.drawable.ic_manage_accounts),
+ "Open Google Password Manager", "beckett-family@gmail.com"
+ ),
+ )
+ ).setRemoteEntry(
+ newRemoteEntry("key4", "subkey-1")
).build(),
- GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
+ GetCredentialProviderData.Builder("com.dashlane")
.setCredentialEntries(
listOf<Entry>(
- newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
- 20, 7, 27, 30000),
- newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
- 20, 7, 27, 31000),
+ newGetEntry(
+ "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
+ "elisa.family@outlook.com", null, 600L
+ ),
+ newGetEntry(
+ "key1", "subkey-2", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+ "elisa.family@outlook.com", null, 100L
+ ),
)
+ ).setAuthenticationEntry(
+ newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL)
).setActionChips(
- listOf<Entry>(
- newEntry("key2", "subkey-3", "Manage Accounts",
- 20, 7, 27, 40000),
- ),
+ listOf(
+ newActionEntry(
+ "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
+ Icon.createWithResource(context, R.drawable.ic_face),
+ "Open Enpass"
+ ),
+ )
).build(),
)
}
- private fun newEntry(
+ private fun newActionEntry(
+ key: String,
+ subkey: String,
+ credentialType: String,
+ icon: Icon,
+ text: String,
+ subtext: String? = null,
+ ): Entry {
+ val slice = Slice.Builder(
+ Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+ ).addText(
+ text, null, listOf(Entry.HINT_ACTION_TITLE)
+ ).addIcon(icon, null, listOf(Entry.HINT_ACTION_ICON))
+ if (subtext != null) {
+ slice.addText(subtext, null, listOf(Entry.HINT_ACTION_SUBTEXT))
+ }
+ return Entry(
+ key,
+ subkey,
+ slice.build()
+ )
+ }
+
+ private fun newAuthenticationEntry(
+ key: String,
+ subkey: String,
+ credentialType: String,
+ ): Entry {
+ val slice = Slice.Builder(
+ Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+ )
+ return Entry(
+ key,
+ subkey,
+ slice.build()
+ )
+ }
+
+ private fun newGetEntry(
+ key: String,
+ subkey: String,
+ credentialType: String,
+ credentialTypeDisplayName: String,
+ userName: String,
+ userDisplayName: String?,
+ lastUsedTimeMillis: Long?,
+ ): Entry {
+ val slice = Slice.Builder(
+ Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+ ).addText(
+ credentialTypeDisplayName, null, listOf(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)
+ ).addText(
+ userName, null, listOf(Entry.HINT_USER_NAME)
+ ).addIcon(
+ Icon.createWithResource(context, R.drawable.ic_passkey),
+ null,
+ listOf(Entry.HINT_PROFILE_ICON))
+ if (userDisplayName != null) {
+ slice.addText(userDisplayName, null, listOf(Entry.HINT_PASSKEY_USER_DISPLAY_NAME))
+ }
+ if (lastUsedTimeMillis != null) {
+ slice.addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
+ }
+ return Entry(
+ key,
+ subkey,
+ slice.build()
+ )
+ }
+
+ private fun newCreateEntry(
key: String,
subkey: String,
providerDisplayName: String,
@@ -258,16 +388,42 @@
)
}
- private fun testRequestInfo(): RequestInfo {
- val data = Bundle()
+ private fun newRemoteEntry(
+ key: String,
+ subkey: String,
+ ): Entry {
+ return Entry(
+ key,
+ subkey,
+ Slice.Builder(
+ Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
+ ).build()
+ )
+ }
+
+ private fun testCreateRequestInfo(): RequestInfo {
+ val data = toBundle("beckett-bakert@gmail.com", "password123")
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
- // TODO: use the jetpack type and utils once defined.
- TYPE_PUBLIC_KEY_CREDENTIAL,
+ TYPE_PASSWORD_CREDENTIAL,
data
),
/*isFirstUsage=*/false,
+ "tribank"
+ )
+ }
+
+ private fun testGetRequestInfo(): RequestInfo {
+ val data = Bundle()
+ return RequestInfo.newGetRequestInfo(
+ Binder(),
+ GetCredentialRequest.Builder()
+ .addGetCredentialOption(
+ GetCredentialOption(TYPE_PUBLIC_KEY_CREDENTIAL, Bundle())
+ )
+ .build(),
+ /*isFirstUsage=*/false,
"tribank.us"
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 78edaa9..1041a33 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -28,8 +28,8 @@
import com.android.credentialmanager.common.DialogType
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ResultState
-import com.android.credentialmanager.createflow.CreatePasskeyScreen
-import com.android.credentialmanager.createflow.CreatePasskeyViewModel
+import com.android.credentialmanager.createflow.CreateCredentialScreen
+import com.android.credentialmanager.createflow.CreateCredentialViewModel
import com.android.credentialmanager.getflow.GetCredentialScreen
import com.android.credentialmanager.getflow.GetCredentialViewModel
import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
@@ -63,12 +63,12 @@
val dialogType = DialogType.toDialogType(operationType)
when (dialogType) {
DialogType.CREATE_PASSKEY -> {
- val viewModel: CreatePasskeyViewModel = viewModel()
+ val viewModel: CreateCredentialViewModel = viewModel()
viewModel.observeDialogResult().observe(
this@CredentialSelectorActivity,
onCancel
)
- CreatePasskeyScreen(viewModel = viewModel)
+ CreateCredentialScreen(viewModel = viewModel)
}
DialogType.GET_CREDENTIALS -> {
val viewModel: GetCredentialViewModel = viewModel()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 3a8e975..fad9364 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -16,13 +16,23 @@
package com.android.credentialmanager
+import android.content.ComponentName
import android.content.Context
+import android.content.pm.PackageManager
import android.credentials.ui.Entry
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.CreateCredentialProviderData
+import android.credentials.ui.DisabledProviderData
+import android.graphics.drawable.Drawable
import com.android.credentialmanager.createflow.CreateOptionInfo
-import com.android.credentialmanager.getflow.CredentialOptionInfo
+import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.getflow.ActionEntryInfo
+import com.android.credentialmanager.getflow.AuthenticationEntryInfo
+import com.android.credentialmanager.getflow.CredentialEntryInfo
import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.getflow.RemoteEntryInfo
+import com.android.credentialmanager.jetpack.provider.ActionUi
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
/** Utility functions for converting CredentialManager data structures to or from UI formats. */
@@ -33,36 +43,109 @@
providerDataList: List<GetCredentialProviderData>,
context: Context,
): List<ProviderInfo> {
+ val packageManager = context.packageManager
return providerDataList.map {
+ // TODO: get from the actual service info
+ val pkgInfo = packageManager
+ .getPackageInfo(it.providerFlattenedComponentName,
+ PackageManager.PackageInfoFlags.of(0))
+ val providerDisplayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString()
+ // TODO: decide what to do when failed to load a provider icon
+ val providerIcon = pkgInfo.applicationInfo.loadIcon(packageManager)!!
ProviderInfo(
- // TODO: replace to extract from the service data structure when available
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = it.providerFlattenedComponentName,
- // TODO: get the service display name and icon from the component name.
- displayName = it.providerFlattenedComponentName,
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context),
+ id = it.providerFlattenedComponentName,
+ // TODO: decide what to do when failed to load a provider icon
+ icon = providerIcon,
+ displayName = providerDisplayName,
+ credentialEntryList = getCredentialOptionInfoList(
+ it.providerFlattenedComponentName, it.credentialEntries, context),
+ authenticationEntry = getAuthenticationEntry(
+ it.providerFlattenedComponentName,
+ providerDisplayName,
+ providerIcon,
+ it.authenticationEntry),
+ remoteEntry = getRemoteEntry(it.providerFlattenedComponentName, it.remoteEntry),
+ actionEntryList = getActionEntryList(
+ it.providerFlattenedComponentName, it.actionChips, context),
)
}
}
/* From service data structure to UI credential entry list representation. */
- private fun toCredentialOptionInfoList(
+ private fun getCredentialOptionInfoList(
+ providerId: String,
credentialEntries: List<Entry>,
context: Context,
- ): List<CredentialOptionInfo> {
+ ): List<CredentialEntryInfo> {
return credentialEntries.map {
val credentialEntryUi = CredentialEntryUi.fromSlice(it.slice)
// Consider directly move the UI object into the class.
- return@map CredentialOptionInfo(
- // TODO: remove fallbacks
- icon = credentialEntryUi.icon?.loadDrawable(context)
- ?: context.getDrawable(R.drawable.ic_passkey)!!,
+ return@map CredentialEntryInfo(
+ providerId = providerId,
entryKey = it.key,
entrySubkey = it.subkey,
- usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
+ credentialType = credentialEntryUi.credentialType.toString(),
+ credentialTypeDisplayName = credentialEntryUi.credentialTypeDisplayName.toString(),
+ userName = credentialEntryUi.userName.toString(),
+ displayName = credentialEntryUi.userDisplayName?.toString(),
+ // TODO: proper fallback
+ icon = credentialEntryUi.entryIcon.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_passkey)!!,
+ lastUsedTimeMillis = credentialEntryUi.lastUsedTimeMillis,
+ )
+ }
+ }
+
+ private fun getAuthenticationEntry(
+ providerId: String,
+ providerDisplayName: String,
+ providerIcon: Drawable,
+ authEntry: Entry?,
+ ): AuthenticationEntryInfo? {
+ // TODO: should also call fromSlice after getting the official jetpack code.
+
+ if (authEntry == null) {
+ return null
+ }
+ return AuthenticationEntryInfo(
+ providerId = providerId,
+ entryKey = authEntry.key,
+ entrySubkey = authEntry.subkey,
+ title = providerDisplayName,
+ icon = providerIcon,
+ )
+ }
+
+ private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? {
+ // TODO: should also call fromSlice after getting the official jetpack code.
+ if (remoteEntry == null) {
+ return null
+ }
+ return RemoteEntryInfo(
+ providerId = providerId,
+ entryKey = remoteEntry.key,
+ entrySubkey = remoteEntry.subkey,
+ )
+ }
+
+ private fun getActionEntryList(
+ providerId: String,
+ actionEntries: List<Entry>,
+ context: Context,
+ ): List<ActionEntryInfo> {
+ return actionEntries.map {
+ val actionEntryUi = ActionUi.fromSlice(it.slice)
+
+ return@map ActionEntryInfo(
+ providerId = providerId,
+ entryKey = it.key,
+ entrySubkey = it.subkey,
+ title = actionEntryUi.text.toString(),
+ // TODO: gracefully fail
+ icon = actionEntryUi.icon.loadDrawable(context)!!,
+ subTitle = actionEntryUi.subtext?.toString(),
)
}
}
@@ -72,18 +155,50 @@
class CreateFlowUtils {
companion object {
- fun toProviderList(
+ fun toEnabledProviderList(
providerDataList: List<CreateCredentialProviderData>,
context: Context,
- ): List<com.android.credentialmanager.createflow.ProviderInfo> {
+ ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
+ // TODO: get from the actual service info
+ val packageManager = context.packageManager
+
return providerDataList.map {
- com.android.credentialmanager.createflow.ProviderInfo(
- // TODO: replace to extract from the service data structure when available
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ val componentName = ComponentName.unflattenFromString(it.providerFlattenedComponentName)
+ var packageName = componentName?.packageName
+ if (componentName == null) {
+ // TODO: Remove once test data is fixed
+ packageName = it.providerFlattenedComponentName
+ }
+
+ val pkgInfo = packageManager
+ .getPackageInfo(packageName!!,
+ PackageManager.PackageInfoFlags.of(0))
+ com.android.credentialmanager.createflow.EnabledProviderInfo(
+ // TODO: decide what to do when failed to load a provider icon
+ icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
name = it.providerFlattenedComponentName,
- displayName = it.providerFlattenedComponentName,
+ displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
createOptions = toCreationOptionInfoList(it.saveEntries, context),
isDefault = it.isDefaultProvider,
+ remoteEntry = toRemoteInfo(it.remoteEntry),
+ )
+ }
+ }
+
+ fun toDisabledProviderList(
+ providerDataList: List<DisabledProviderData>,
+ context: Context,
+ ): List<com.android.credentialmanager.createflow.DisabledProviderInfo> {
+ // TODO: get from the actual service info
+ val packageManager = context.packageManager
+ return providerDataList.map {
+ val pkgInfo = packageManager
+ .getPackageInfo(it.providerFlattenedComponentName,
+ PackageManager.PackageInfoFlags.of(0))
+ com.android.credentialmanager.createflow.DisabledProviderInfo(
+ icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
+ name = it.providerFlattenedComponentName,
+ displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
)
}
}
@@ -111,5 +226,17 @@
)
}
}
+
+ private fun toRemoteInfo(
+ remoteEntry: Entry?,
+ ): RemoteInfo? {
+ // TODO: should also call fromSlice after getting the official jetpack code.
+ return if (remoteEntry != null) {
+ RemoteInfo(
+ entryKey = remoteEntry.key,
+ entrySubkey = remoteEntry.subkey,
+ )
+ } else null
+ }
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index f1f453d..61e11fe 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -62,6 +62,7 @@
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlin.math.max
@@ -318,7 +319,7 @@
rememberModalBottomSheetState(Hidden),
sheetShape: Shape = MaterialTheme.shapes.large,
sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
- sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ sheetBackgroundColor: Color = ModalBottomSheetDefaults.scrimColor,
sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
content: @Composable () -> Unit
@@ -476,5 +477,5 @@
*/
val scrimColor: Color
@Composable
- get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
+ get() = LocalAndroidColorScheme.current.colorSurfaceHighlight
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
similarity index 66%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
index 817c209f..177d0e0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
@@ -14,8 +14,15 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package com.android.credentialmanager.common.ui
-import com.android.settingslib.spa.framework.EntryProvider
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
-class GalleryEntryProvider : EntryProvider()
+@Composable
+fun CancelButton(text: String, onClick: () -> Unit) {
+ TextButton(onClick = onClick) {
+ Text(text = text)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
rename to packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
index 7a2de7b..b2b0bdc 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.credentialmanager.common.ui
-import androidx.annotation.StringRes;
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
-import com.android.internal.R;
-
-/** Helper class for referencing resources */
-class ChooserSelectorResourceHelper {
-
- private ChooserSelectorResourceHelper() {
+@Composable
+fun ConfirmButton(text: String, onClick: () -> Unit) {
+ FilledTonalButton(onClick = onClick) {
+ Text(text = text)
}
-
- @StringRes
- static final int CONFIG_CHOOSER_ACTIVITY = R.string.config_chooserActivity;
-}
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
new file mode 100644
index 0000000..51a1cbb
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.credentialmanager.common.ui
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.SuggestionChipDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Entry(
+ onClick: () -> Unit,
+ label: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: @Composable (() -> Unit)? = null,
+) {
+ SuggestionChip(
+ modifier = modifier.fillMaxWidth(),
+ onClick = onClick,
+ shape = EntryShape.FullSmallRoundedCorner,
+ label = label,
+ icon = icon,
+ border = null,
+ colors = SuggestionChipDefaults.suggestionChipColors(
+ containerColor = LocalAndroidColorScheme.current.colorSurface,
+ ),
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TransparentBackgroundEntry(
+ onClick: () -> Unit,
+ label: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: @Composable (() -> Unit)? = null,
+) {
+ SuggestionChip(
+ modifier = modifier.fillMaxWidth(),
+ onClick = onClick,
+ label = label,
+ icon = icon,
+ border = null,
+ colors = SuggestionChipDefaults.suggestionChipColors(
+ containerColor = Color.Transparent,
+ ),
+ )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
new file mode 100644
index 0000000..27d366d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -0,0 +1,659 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.android.credentialmanager.createflow
+
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Card
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.outlined.NewReleases
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.ModalBottomSheetLayout
+import com.android.credentialmanager.common.material.ModalBottomSheetValue
+import com.android.credentialmanager.common.material.rememberModalBottomSheetState
+import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CreateCredentialScreen(
+ viewModel: CreateCredentialViewModel,
+) {
+ val state = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Expanded,
+ skipHalfExpanded = true
+ )
+ ModalBottomSheetLayout(
+ sheetState = state,
+ sheetContent = {
+ val uiState = viewModel.uiState
+ when (uiState.currentScreenState) {
+ CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
+ onConfirm = viewModel::onConfirmIntro,
+ onCancel = viewModel::onCancel,
+ )
+ CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
+ enabledProviderList = uiState.enabledProviders,
+ onCancel = viewModel::onCancel,
+ onProviderSelected = viewModel::onProviderSelected
+ )
+ CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
+ onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
+ onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
+ onCancel = viewModel::onCancel,
+ multiProvider = uiState.enabledProviders.size > 1,
+ onMoreOptionsSelected = viewModel::onMoreOptionsSelected
+ )
+ CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ enabledProviderList = uiState.enabledProviders,
+ disabledProviderList = uiState.disabledProviders,
+ onBackButtonSelected = viewModel::onBackButtonSelected,
+ onOptionSelected = viewModel::onMoreOptionsRowSelected,
+ onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
+ onRemoteEntrySelected = viewModel::onRemoteEntrySelected
+ )
+ CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
+ )
+ }
+ },
+ scrimColor = MaterialTheme.colorScheme.scrim,
+ sheetShape = EntryShape.TopRoundedCorner,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ if (state.currentValue == ModalBottomSheetValue.Hidden) {
+ viewModel.onCancel()
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ConfirmationCard(
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Card() {
+ Column() {
+ Icon(
+ painter = painterResource(R.drawable.ic_passkey),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+ )
+ Text(
+ text = stringResource(R.string.passkey_creation_intro_title),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Text(
+ text = stringResource(R.string.passkey_creation_intro_body),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(horizontal = 28.dp)
+ )
+ Divider(
+ thickness = 48.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onClick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ProviderSelectionCard(
+ enabledProviderList: List<EnabledProviderInfo>,
+ onProviderSelected: (String) -> Unit,
+ onCancel: () -> Unit
+) {
+ Card() {
+ Column() {
+ Text(
+ text = stringResource(R.string.choose_provider_title),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ )
+ Text(
+ text = stringResource(R.string.choose_provider_body),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(horizontal = 28.dp)
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Card(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ enabledProviderList.forEach {
+ item {
+ ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
+ }
+ }
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(stringResource(R.string.string_cancel), onCancel)
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsSelectionCard(
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>?,
+ onBackButtonSelected: () -> Unit,
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledPasswordManagerSelected: () -> Unit,
+ onRemoteEntrySelected: () -> Unit,
+) {
+ Card() {
+ Column() {
+ TopAppBar(
+ title = {
+ Text(
+ text = when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to)
+ else -> stringResource(R.string.save_sign_in_to)
+ },
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = onBackButtonSelected) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ stringResource(R.string.accessibility_back_arrow_button))
+ }
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors
+ (containerColor = Color.Transparent),
+ )
+ Divider(
+ thickness = 8.dp,
+ color = Color.Transparent
+ )
+ Card(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ enabledProviderList.forEach { enabledProviderInfo ->
+ enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+ item {
+ MoreOptionsInfoRow(
+ providerInfo = enabledProviderInfo,
+ createOptionInfo = createOptionInfo,
+ onOptionSelected = {
+ onOptionSelected(ActiveEntry(enabledProviderInfo, createOptionInfo))
+ })
+ }
+ }
+ }
+ if (disabledProviderList != null) {
+ item {
+ MoreOptionsDisabledProvidersRow(
+ disabledProviders = disabledProviderList,
+ onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected,
+ )
+ }
+ }
+ var hasRemoteInfo = false
+ enabledProviderList.forEach {
+ if (it.remoteEntry != null) {
+ hasRemoteInfo = true
+ }
+ }
+ if (hasRemoteInfo) {
+ item {
+ RemoteEntryRow(
+ onRemoteEntrySelected = onRemoteEntrySelected,
+ )
+ }
+ }
+ }
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 40.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsRowIntroCard(
+ providerInfo: EnabledProviderInfo,
+ onDefaultOrNotSelected: () -> Unit,
+) {
+ Card() {
+ Column() {
+ Icon(
+ Icons.Outlined.NewReleases,
+ contentDescription = null,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
+ )
+ Text(
+ text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ text = stringResource(R.string.confirm_default_or_use_once_description),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.use_once),
+ onClick = onDefaultOrNotSelected
+ )
+ ConfirmButton(
+ stringResource(R.string.set_as_default),
+ onClick = onDefaultOrNotSelected
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 40.dp)
+ )
+ }
+ }
+}
+
+@Composable
+fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
+ Entry(
+ onClick = {onProviderSelected(providerInfo.name)},
+ icon = {
+ Image(modifier = Modifier.size(32.dp).padding(start = 10.dp),
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ // painter = painterResource(R.drawable.ic_passkey),
+ // TODO: add description.
+ contentDescription = "")
+ },
+ label = {
+ Text(
+ text = providerInfo.displayName,
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 18.dp)
+ )
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CreationSelectionCard(
+ requestDisplayInfo: RequestDisplayInfo,
+ providerInfo: ProviderInfo,
+ createOptionInfo: CreateOptionInfo,
+ onOptionSelected: () -> Unit,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+ multiProvider: Boolean,
+ onMoreOptionsSelected: () -> Unit,
+) {
+ Card() {
+ Column() {
+ Icon(
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp).size(32.dp)
+ )
+ Text(
+ text = when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.choose_create_option_passkey_title,
+ providerInfo.displayName)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.choose_create_option_password_title,
+ providerInfo.displayName)
+ else -> stringResource(R.string.choose_create_option_sign_in_title,
+ providerInfo.displayName)
+ },
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ if (createOptionInfo.userProviderDisplayName != null) {
+ Text(
+ text = stringResource(
+ R.string.choose_create_option_description,
+ requestDisplayInfo.appDomainName,
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkey)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.password)
+ else -> stringResource(R.string.sign_ins)
+ },
+ providerInfo.displayName,
+ createOptionInfo.userProviderDisplayName
+ ),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ )
+ }
+ Card(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ item {
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ createOptionInfo = createOptionInfo,
+ onOptionSelected = onOptionSelected
+ )
+ }
+ }
+ }
+ if (multiProvider) {
+ TextButton(
+ onClick = onMoreOptionsSelected,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)){
+ Text(
+ text =
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL ->
+ stringResource(R.string.string_create_in_another_place)
+ else -> stringResource(R.string.string_save_to_another_place)},
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onClick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PrimaryCreateOptionRow(
+ requestDisplayInfo: RequestDisplayInfo,
+ createOptionInfo: CreateOptionInfo,
+ onOptionSelected: () -> Unit
+) {
+ Entry(
+ onClick = onOptionSelected,
+ icon = {
+ Image(modifier = Modifier.size(32.dp).padding(start = 10.dp),
+ bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ contentDescription = null)
+ },
+ label = {
+ Column() {
+ // TODO: Add the function to hide/view password when the type is create password
+ if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
+ requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = requestDisplayInfo.subtitle,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ } else {
+ Text(
+ text = requestDisplayInfo.subtitle,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
+ )
+ }
+ }
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsInfoRow(
+ providerInfo: EnabledProviderInfo,
+ createOptionInfo: CreateOptionInfo,
+ onOptionSelected: () -> Unit
+) {
+ Entry(
+ onClick = onOptionSelected,
+ icon = {
+ Image(modifier = Modifier.size(32.dp).padding(start = 16.dp),
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ contentDescription = null)
+ },
+ label = {
+ Column() {
+ Text(
+ text = providerInfo.displayName,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, start = 16.dp)
+ )
+ if (createOptionInfo.userProviderDisplayName != null) {
+ Text(
+ text = createOptionInfo.userProviderDisplayName,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 16.dp)
+ )
+ }
+ if (createOptionInfo.passwordCount != null && createOptionInfo.passkeyCount != null) {
+ Text(
+ text =
+ stringResource(
+ R.string.more_options_usage_passwords_passkeys,
+ createOptionInfo.passwordCount,
+ createOptionInfo.passkeyCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
+ )
+ } else if (createOptionInfo.passwordCount != null) {
+ Text(
+ text =
+ stringResource(
+ R.string.more_options_usage_passwords,
+ createOptionInfo.passwordCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
+ )
+ } else if (createOptionInfo.passkeyCount != null) {
+ Text(
+ text =
+ stringResource(
+ R.string.more_options_usage_passkeys,
+ createOptionInfo.passkeyCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
+ )
+ } else if (createOptionInfo.totalCredentialCount != null) {
+ // TODO: Handle the case when there is total count
+ // but no passwords and passkeys after design is set
+ }
+ }
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsDisabledProvidersRow(
+ disabledProviders: List<ProviderInfo>,
+ onDisabledPasswordManagerSelected: () -> Unit,
+) {
+ Entry(
+ onClick = onDisabledPasswordManagerSelected,
+ icon = {
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = null,
+ modifier = Modifier.padding(start = 16.dp)
+ )
+ },
+ label = {
+ Column() {
+ Text(
+ text = stringResource(R.string.other_password_manager),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, start = 16.dp)
+ )
+ Text(
+ text = disabledProviders.joinToString(separator = ", "){ it.displayName },
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 16.dp)
+ )
+ }
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RemoteEntryRow(
+ onRemoteEntrySelected: () -> Unit,
+) {
+ Entry(
+ onClick = onRemoteEntrySelected,
+ icon = {
+ Icon(
+ painter = painterResource(R.drawable.ic_other_devices),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.padding(start = 18.dp)
+ )
+ },
+ label = {
+ Column() {
+ Text(
+ text = stringResource(R.string.another_device),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ )
+ }
+ }
+ )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
similarity index 82%
rename from packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 2e9758a..6be019f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -27,18 +27,19 @@
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ResultState
-data class CreatePasskeyUiState(
- val providers: List<ProviderInfo>,
+data class CreateCredentialUiState(
+ val enabledProviders: List<EnabledProviderInfo>,
+ val disabledProviders: List<DisabledProviderInfo>? = null,
val currentScreenState: CreateScreenState,
val requestDisplayInfo: RequestDisplayInfo,
val activeEntry: ActiveEntry? = null,
)
-class CreatePasskeyViewModel(
+class CreateCredentialViewModel(
credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
) : ViewModel() {
- var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())
+ var uiState by mutableStateOf(credManRepo.createCredentialInitialUiState())
private set
val dialogResult: MutableLiveData<DialogResult> by lazy {
@@ -50,15 +51,15 @@
}
fun onConfirmIntro() {
- if (uiState.providers.size > 1) {
+ if (uiState.enabledProviders.size > 1) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.PROVIDER_SELECTION
)
- } else if (uiState.providers.size == 1){
+ } else if (uiState.enabledProviders.size == 1){
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- activeEntry = ActiveEntry(uiState.providers.first(),
- uiState.providers.first().createOptions.first())
+ activeEntry = ActiveEntry(uiState.enabledProviders.first(),
+ uiState.enabledProviders.first().createOptions.first())
)
} else {
throw java.lang.IllegalStateException("Empty provider list.")
@@ -73,8 +74,8 @@
)
}
- fun getProviderInfoByName(providerName: String): ProviderInfo {
- return uiState.providers.single {
+ fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
+ return uiState.enabledProviders.single {
it.name.equals(providerName)
}
}
@@ -98,6 +99,14 @@
)
}
+ fun onDisabledPasswordManagerSelected() {
+ // TODO: Complete this function
+ }
+
+ fun onRemoteEntrySelected() {
+ // TODO: Complete this function
+ }
+
fun onCancel() {
CredentialManagerRepo.getInstance().onCancel()
dialogResult.value = DialogResult(ResultState.CANCELED)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index d7e5ee8..1ab234a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -18,14 +18,27 @@
import android.graphics.drawable.Drawable
-data class ProviderInfo(
+open class ProviderInfo(
val icon: Drawable,
val name: String,
val displayName: String,
- val createOptions: List<CreateOptionInfo>,
- val isDefault: Boolean,
)
+class EnabledProviderInfo(
+ icon: Drawable,
+ name: String,
+ displayName: String,
+ var createOptions: List<CreateOptionInfo>,
+ val isDefault: Boolean,
+ var remoteEntry: RemoteInfo?,
+) : ProviderInfo(icon, name, displayName)
+
+class DisabledProviderInfo(
+ icon: Drawable,
+ name: String,
+ displayName: String,
+) : ProviderInfo(icon, name, displayName)
+
open class EntryInfo (
val entryKey: String,
val entrySubkey: String,
@@ -34,18 +47,23 @@
class CreateOptionInfo(
entryKey: String,
entrySubkey: String,
- val userProviderDisplayName: String,
+ val userProviderDisplayName: String?,
val credentialTypeIcon: Drawable,
val profileIcon: Drawable,
- val passwordCount: Int,
- val passkeyCount: Int,
- val totalCredentialCount: Int,
+ val passwordCount: Int?,
+ val passkeyCount: Int?,
+ val totalCredentialCount: Int?,
val lastUsedTimeMillis: Long?,
) : EntryInfo(entryKey, entrySubkey)
+class RemoteInfo(
+ entryKey: String,
+ entrySubkey: String,
+) : EntryInfo(entryKey, entrySubkey)
+
data class RequestDisplayInfo(
- val userName: String,
- val displayName: String,
+ val title: String,
+ val subtitle: String,
val type: String,
val appDomainName: String,
)
@@ -55,7 +73,7 @@
* user selects a different entry on the more option page.
*/
data class ActiveEntry (
- val activeProvider: ProviderInfo,
+ val activeProvider: EnabledProviderInfo,
val activeEntryInfo: EntryInfo,
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
deleted file mode 100644
index 278b835..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ /dev/null
@@ -1,534 +0,0 @@
-package com.android.credentialmanager.createflow
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material3.Card
-import androidx.compose.material3.Divider
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FilledTonalButton
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SuggestionChip
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.drawable.toBitmap
-import com.android.credentialmanager.R
-import com.android.credentialmanager.common.material.ModalBottomSheetLayout
-import com.android.credentialmanager.common.material.ModalBottomSheetValue
-import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
-import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun CreatePasskeyScreen(
- viewModel: CreatePasskeyViewModel,
-) {
- val state = rememberModalBottomSheetState(
- initialValue = ModalBottomSheetValue.Expanded,
- skipHalfExpanded = true
- )
- ModalBottomSheetLayout(
- sheetState = state,
- sheetContent = {
- val uiState = viewModel.uiState
- when (uiState.currentScreenState) {
- CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
- onConfirm = viewModel::onConfirmIntro,
- onCancel = viewModel::onCancel,
- )
- CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- providerList = uiState.providers,
- onCancel = viewModel::onCancel,
- onProviderSelected = viewModel::onProviderSelected
- )
- CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- providerInfo = uiState.activeEntry?.activeProvider!!,
- createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
- onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
- onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
- onCancel = viewModel::onCancel,
- multiProvider = uiState.providers.size > 1,
- onMoreOptionsSelected = viewModel::onMoreOptionsSelected
- )
- CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
- providerList = uiState.providers,
- onBackButtonSelected = viewModel::onBackButtonSelected,
- onOptionSelected = viewModel::onMoreOptionsRowSelected
- )
- CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
- providerInfo = uiState.activeEntry?.activeProvider!!,
- onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
- )
- }
- },
- scrimColor = MaterialTheme.colorScheme.scrim,
- sheetShape = MaterialTheme.shapes.medium,
- ) {}
- LaunchedEffect(state.currentValue) {
- if (state.currentValue == ModalBottomSheetValue.Hidden) {
- viewModel.onCancel()
- }
- }
-}
-
-@Composable
-fun ConfirmationCard(
- onConfirm: () -> Unit,
- onCancel: () -> Unit,
-) {
- Card() {
- Column() {
- Icon(
- painter = painterResource(R.drawable.ic_passkey),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
- )
- Text(
- text = stringResource(R.string.passkey_creation_intro_title),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- )
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Text(
- text = stringResource(R.string.passkey_creation_intro_body),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(horizontal = 28.dp)
- )
- Divider(
- thickness = 48.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
- )
- ConfirmButton(
- stringResource(R.string.string_continue),
- onClick = onConfirm
- )
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ProviderSelectionCard(
- providerList: List<ProviderInfo>,
- onProviderSelected: (String) -> Unit,
- onCancel: () -> Unit
-) {
- Card() {
- Column() {
- Text(
- text = stringResource(R.string.choose_provider_title),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
- )
- Text(
- text = stringResource(R.string.choose_provider_body),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(horizontal = 28.dp)
- )
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Card(
- shape = MaterialTheme.shapes.large,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- providerList.forEach {
- item {
- ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
- }
- }
- }
- }
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.Start,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(stringResource(R.string.string_cancel), onCancel)
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MoreOptionsSelectionCard(
- providerList: List<ProviderInfo>,
- onBackButtonSelected: () -> Unit,
- onOptionSelected: (ActiveEntry) -> Unit
-) {
- Card() {
- Column() {
- TopAppBar(
- title = {
- Text(
- text = stringResource(R.string.string_more_options),
- style = MaterialTheme.typography.titleMedium
- )
- },
- navigationIcon = {
- IconButton(onClick = onBackButtonSelected) {
- Icon(
- Icons.Filled.ArrowBack,
- "backIcon")
- }
- }
- )
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Text(
- text = stringResource(R.string.create_passkey_at),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(horizontal = 28.dp),
- textAlign = TextAlign.Center
- )
- Card(
- shape = MaterialTheme.shapes.large,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- // TODO: change the order according to usage frequency
- providerList.forEach { providerInfo ->
- providerInfo.createOptions.forEach { createOptionInfo ->
- item {
- MoreOptionsInfoRow(
- providerInfo = providerInfo,
- createOptionInfo = createOptionInfo,
- onOptionSelected = {
- onOptionSelected(ActiveEntry(providerInfo, createOptionInfo))
- })
- }
- }
- }
- }
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 40.dp)
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MoreOptionsRowIntroCard(
- providerInfo: ProviderInfo,
- onDefaultOrNotSelected: () -> Unit,
-) {
- Card() {
- Column() {
- Text(
- text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(
- stringResource(R.string.use_once),
- onClick = onDefaultOrNotSelected
- )
- ConfirmButton(
- stringResource(R.string.set_as_default),
- onClick = onDefaultOrNotSelected
- )
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 40.dp)
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
- onClick = {onProviderSelected(providerInfo.name)},
- icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
- // painter = painterResource(R.drawable.ic_passkey),
- // TODO: add description.
- contentDescription = "")
- },
- shape = MaterialTheme.shapes.large,
- label = {
- Text(
- text = providerInfo.displayName,
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(vertical = 18.dp)
- )
- }
- )
-}
-
-@Composable
-fun CancelButton(text: String, onClick: () -> Unit) {
- TextButton(onClick = onClick) {
- Text(text = text)
- }
-}
-
-@Composable
-fun ConfirmButton(text: String, onClick: () -> Unit) {
- FilledTonalButton(onClick = onClick) {
- Text(text = text)
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun CreationSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- providerInfo: ProviderInfo,
- createOptionInfo: CreateOptionInfo,
- onOptionSelected: () -> Unit,
- onConfirm: () -> Unit,
- onCancel: () -> Unit,
- multiProvider: Boolean,
- onMoreOptionsSelected: () -> Unit,
-) {
- Card() {
- Column() {
- Icon(
- bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
- )
- Text(
- text = when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.choose_create_option_passkey_title,
- providerInfo.displayName)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.choose_create_option_password_title,
- providerInfo.displayName)
- else -> stringResource(R.string.choose_create_option_sign_in_title,
- providerInfo.displayName)
- },
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- textAlign = TextAlign.Center,
- )
- Text(
- text = requestDisplayInfo.appDomainName,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- )
- Text(
- text = stringResource(
- R.string.choose_create_option_description,
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
- else -> stringResource(R.string.sign_ins)
- },
- providerInfo.displayName,
- createOptionInfo.userProviderDisplayName
- ),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
- )
- Card(
- shape = MaterialTheme.shapes.large,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- item {
- PrimaryCreateOptionRow(
- requestDisplayInfo = requestDisplayInfo,
- createOptionInfo = createOptionInfo,
- onOptionSelected = onOptionSelected
- )
- }
- }
- }
- if (multiProvider) {
- TextButton(
- onClick = onMoreOptionsSelected,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)){
- Text(
- text =
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL ->
- stringResource(R.string.string_create_in_another_place)
- else -> stringResource(R.string.string_save_to_another_place)},
- textAlign = TextAlign.Center,
- )
- }
- }
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
- ) {
- CancelButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
- )
- ConfirmButton(
- stringResource(R.string.string_continue),
- onClick = onConfirm
- )
- }
- Divider(
- thickness = 18.dp,
- color = Color.Transparent,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun PrimaryCreateOptionRow(
- requestDisplayInfo: RequestDisplayInfo,
- createOptionInfo: CreateOptionInfo,
- onOptionSelected: () -> Unit
-) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
- onClick = onOptionSelected,
- icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
- contentDescription = stringResource(R.string.createOptionInfo_icon_description))
- },
- shape = MaterialTheme.shapes.large,
- label = {
- Column() {
- Text(
- text = requestDisplayInfo.userName,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = requestDisplayInfo.displayName,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
- )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MoreOptionsInfoRow(
- providerInfo: ProviderInfo,
- createOptionInfo: CreateOptionInfo,
- onOptionSelected: () -> Unit
-) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
- onClick = onOptionSelected,
- icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
- // painter = painterResource(R.drawable.ic_passkey),
- // TODO: add description.
- contentDescription = "")
- },
- shape = MaterialTheme.shapes.large,
- label = {
- Column() {
- Text(
- text =
- if (providerInfo.createOptions.size > 1)
- {stringResource(R.string.more_options_title_multiple_options,
- providerInfo.displayName, createOptionInfo.userProviderDisplayName)} else {
- stringResource(R.string.more_options_title_one_option,
- providerInfo.displayName)},
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = stringResource(R.string.more_options_usage_data,
- createOptionInfo.passwordCount, createOptionInfo.passkeyCount),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- }
- }
- )
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 7635021..19a032f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -16,38 +16,49 @@
package com.android.credentialmanager.getflow
+import android.text.TextUtils
+
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.createflow.CancelButton
+import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GetCredentialScreen(
viewModel: GetCredentialViewModel,
@@ -61,14 +72,19 @@
sheetContent = {
val uiState = viewModel.uiState
when (uiState.currentScreenState) {
- GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(
+ GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
- providerInfo = uiState.selectedProvider!!,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
onCancel = viewModel::onCancel,
- onOptionSelected = viewModel::onCredentailSelected,
- multiProvider = uiState.providers.size > 1,
onMoreOptionSelected = viewModel::onMoreOptionSelected,
)
+ GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
+ providerInfoList = uiState.providerInfoList,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
+ onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
+ )
}
},
scrimColor = Color.Transparent,
@@ -81,40 +97,35 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class)
+/** Draws the primary credential selection page. */
@Composable
-fun CredentialSelectionCard(
+fun PrimarySelectionCard(
requestDisplayInfo: RequestDisplayInfo,
- providerInfo: ProviderInfo,
- onOptionSelected: (String, String) -> Unit,
+ providerDisplayInfo: ProviderDisplayInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
onCancel: () -> Unit,
- multiProvider: Boolean,
onMoreOptionSelected: () -> Unit,
) {
+ val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val authenticationEntryList = providerDisplayInfo.authenticationEntryList
Card() {
Column() {
- Icon(
- bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
- )
Text(
- text = stringResource(R.string.choose_sign_in_title),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier
- .padding(all = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
+ modifier = Modifier.padding(all = 24.dp),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.headlineSmall,
+ text = stringResource(
+ if (sortedUserNameToCredentialEntryList.size == 1) {
+ if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
+ .first().credentialType == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
+ )
+ R.string.get_dialog_title_use_passkey_for
+ else R.string.get_dialog_title_use_sign_in_for
+ } else R.string.get_dialog_title_choose_sign_in_for,
+ requestDisplayInfo.appDomainName
+ ),
)
- Text(
- text = requestDisplayInfo.appDomainName,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(horizontal = 28.dp)
- )
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
+
Card(
shape = MaterialTheme.shapes.medium,
modifier = Modifier
@@ -124,15 +135,20 @@
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
- providerInfo.credentialOptions.forEach {
- item {
- CredentialOptionRow(credentialOptionInfo = it, onOptionSelected = onOptionSelected)
- }
+ items(sortedUserNameToCredentialEntryList) {
+ CredentialEntryRow(
+ credentialEntryInfo = it.sortedCredentialEntryList.first(),
+ onEntrySelected = onEntrySelected,
+ )
}
- if (multiProvider) {
- item {
- MoreOptionRow(onSelect = onMoreOptionSelected)
- }
+ items(authenticationEntryList) {
+ AuthenticationEntryRow(
+ authenticationEntryInfo = it,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ item {
+ SignInAnotherWayRow(onSelect = onMoreOptionSelected)
}
}
}
@@ -155,32 +171,237 @@
}
}
+/** Draws the secondary credential selection page, where all sign-in options are listed. */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun CredentialOptionRow(
- credentialOptionInfo: CredentialOptionInfo,
- onOptionSelected: (String, String) -> Unit,
+fun AllSignInOptionCard(
+ providerInfoList: List<ProviderInfo>,
+ providerDisplayInfo: ProviderDisplayInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
+ onBackButtonClicked: () -> Unit,
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
- onClick = {onOptionSelected(credentialOptionInfo.entryKey, credentialOptionInfo.entrySubkey)},
+ val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+ Card() {
+ Column() {
+ TopAppBar(
+ colors = TopAppBarDefaults.smallTopAppBarColors(
+ containerColor = Color.Transparent,
+ ),
+ title = {
+ Text(
+ text = stringResource(R.string.get_dialog_title_sign_in_options),
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = onBackButtonClicked) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ contentDescription = stringResource(R.string.accessibility_back_arrow_button))
+ }
+ },
+ modifier = Modifier.padding(top = 12.dp)
+ )
+
+ Card(
+ shape = MaterialTheme.shapes.large,
+ modifier = Modifier
+ .padding(start = 24.dp, end = 24.dp, bottom = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ // For username
+ items(sortedUserNameToCredentialEntryList) { item ->
+ PerUserNameCredentials(
+ perUserNameCredentialEntryList = item,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ // Locked password manager
+ if (!authenticationEntryList.isEmpty()) {
+ item {
+ LockedCredentials(
+ authenticationEntryList = authenticationEntryList,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ }
+ // From another device
+ val remoteEntry = providerDisplayInfo.remoteEntry
+ if (remoteEntry != null) {
+ item {
+ RemoteEntryCard(
+ remoteEntry = remoteEntry,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ }
+ // Manage sign-ins (action chips)
+ item {
+ ActionChips(providerInfoList = providerInfoList, onEntrySelected = onEntrySelected)
+ }
+ }
+ }
+ }
+ }
+}
+
+// TODO: create separate rows for primary and secondary pages.
+// TODO: reuse rows and columns across types.
+
+@Composable
+fun ActionChips(
+ providerInfoList: List<ProviderInfo>,
+ onEntrySelected: (EntryInfo) -> Unit,
+) {
+ val actionChips = providerInfoList.flatMap { it.actionEntryList }
+ if (actionChips.isEmpty()) {
+ return
+ }
+
+ Text(
+ text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ // TODO: tweak padding.
+ Card(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ actionChips.forEach {
+ ActionEntryRow(it, onEntrySelected)
+ }
+ }
+ }
+}
+
+@Composable
+fun RemoteEntryCard(
+ remoteEntry: RemoteEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
+) {
+ Text(
+ text = stringResource(R.string.get_dialog_heading_from_another_device),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ Card(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ Entry(
+ onClick = {onEntrySelected(remoteEntry)},
+ icon = {
+ Icon(
+ painter = painterResource(R.drawable.ic_other_devices),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.padding(start = 18.dp)
+ )
+ },
+ label = {
+ Text(
+ text = stringResource(R.string.get_dialog_option_headline_use_a_different_device),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ )
+ }
+ )
+ }
+ }
+}
+
+@Composable
+fun LockedCredentials(
+ authenticationEntryList: List<AuthenticationEntryInfo>,
+ onEntrySelected: (EntryInfo) -> Unit,
+) {
+ Text(
+ text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ Card(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ authenticationEntryList.forEach {
+ AuthenticationEntryRow(it, onEntrySelected)
+ }
+ }
+ }
+}
+
+@Composable
+fun PerUserNameCredentials(
+ perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
+ onEntrySelected: (EntryInfo) -> Unit,
+) {
+ Text(
+ text = stringResource(
+ R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ Card(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ perUserNameCredentialEntryList.sortedCredentialEntryList.forEach {
+ CredentialEntryRow(it, onEntrySelected)
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CredentialEntryRow(
+ credentialEntryInfo: CredentialEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
+) {
+ Entry(
+ onClick = {onEntrySelected(credentialEntryInfo)},
icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
+ Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
// TODO: add description.
- contentDescription = "")
+ contentDescription = "")
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
// TODO: fix the text values.
Text(
- text = credentialOptionInfo.entryKey,
+ text = credentialEntryInfo.userName,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(top = 16.dp)
)
Text(
- text = credentialOptionInfo.entrySubkey,
+ text =
+ if (TextUtils.isEmpty(credentialEntryInfo.displayName))
+ credentialEntryInfo.credentialTypeDisplayName
+ else
+ credentialEntryInfo.credentialTypeDisplayName +
+ stringResource(R.string.get_dialog_sign_in_type_username_separator) +
+ credentialEntryInfo.displayName,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
@@ -191,15 +412,77 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun MoreOptionRow(onSelect: () -> Unit) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth().height(52.dp),
+fun AuthenticationEntryRow(
+ authenticationEntryInfo: AuthenticationEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
+) {
+ Entry(
+ onClick = {onEntrySelected(authenticationEntryInfo)},
+ icon = {
+ Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
+ // TODO: add description.
+ contentDescription = "")
+ },
+ label = {
+ Column() {
+ // TODO: fix the text values.
+ Text(
+ text = authenticationEntryInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = stringResource(R.string.locked_credential_entry_label_subtext),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ActionEntryRow(
+ actionEntryInfo: ActionEntryInfo,
+ onEntrySelected: (EntryInfo) -> Unit,
+) {
+ TransparentBackgroundEntry(
+ icon = {
+ Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
+ // TODO: add description.
+ contentDescription = "")
+ },
+ label = {
+ Column() {
+ Text(
+ text = actionEntryInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ )
+ if (actionEntryInfo.subTitle != null) {
+ Text(
+ text = actionEntryInfo.subTitle,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ }
+ },
+ onClick = { onEntrySelected(actionEntryInfo) },
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SignInAnotherWayRow(onSelect: () -> Unit) {
+ Entry(
onClick = onSelect,
- shape = MaterialTheme.shapes.large,
label = {
Text(
- text = stringResource(R.string.string_more_options),
+ text = stringResource(R.string.get_dialog_use_saved_passkey_for),
style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(vertical = 16.dp)
)
}
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 7b6c30a..22370a9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -26,12 +26,14 @@
import com.android.credentialmanager.CredentialManagerRepo
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ResultState
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
+import com.android.internal.util.Preconditions
data class GetCredentialUiState(
- val providers: List<ProviderInfo>,
+ val providerInfoList: List<ProviderInfo>,
val currentScreenState: GetScreenState,
val requestDisplayInfo: RequestDisplayInfo,
- val selectedProvider: ProviderInfo? = null,
+ val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
)
class GetCredentialViewModel(
@@ -49,20 +51,28 @@
return dialogResult
}
- fun onCredentailSelected(entryKey: String, entrySubkey: String) {
- Log.d("Account Selector", "credential selected: {key=$entryKey,subkey=$entrySubkey}")
+ fun onEntrySelected(entry: EntryInfo) {
+ Log.d("Account Selector", "credential selected:" +
+ " {provider=${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
CredentialManagerRepo.getInstance().onOptionSelected(
- uiState.selectedProvider!!.name,
- entryKey,
- entrySubkey
+ entry.providerId,
+ entry.entryKey,
+ entry.entrySubkey
)
- dialogResult.value = DialogResult(
- ResultState.COMPLETE,
- )
+ dialogResult.value = DialogResult(ResultState.COMPLETE)
}
fun onMoreOptionSelected() {
Log.d("Account Selector", "More Option selected")
+ uiState = uiState.copy(
+ currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS
+ )
+ }
+
+ fun onBackToPrimarySelectionScreen() {
+ uiState = uiState.copy(
+ currentScreenState = GetScreenState.PRIMARY_SELECTION
+ )
}
fun onCancel() {
@@ -70,3 +80,83 @@
dialogResult.value = DialogResult(ResultState.CANCELED)
}
}
+
+private fun toProviderDisplayInfo(
+ providerInfoList: List<ProviderInfo>
+): ProviderDisplayInfo {
+
+ val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
+ val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
+ val remoteEntryList = mutableListOf<RemoteEntryInfo>()
+ providerInfoList.forEach { providerInfo ->
+ if (providerInfo.authenticationEntry != null) {
+ authenticationEntryList.add(providerInfo.authenticationEntry)
+ }
+ if (providerInfo.remoteEntry != null) {
+ remoteEntryList.add(providerInfo.remoteEntry)
+ }
+
+ providerInfo.credentialEntryList.forEach {
+ userNameToCredentialEntryMap.compute(
+ it.userName
+ ) {
+ _, v ->
+ if (v == null) {
+ mutableListOf(it)
+ } else {
+ v.add(it)
+ v
+ }
+ }
+ }
+ }
+ // There can only be at most one remote entry
+ // TODO: fail elegantly
+ Preconditions.checkState(remoteEntryList.size <= 1)
+
+ // Compose sortedUserNameToCredentialEntryList
+ val comparator = CredentialEntryInfoComparator()
+ // Sort per username
+ userNameToCredentialEntryMap.values.forEach {
+ it.sortWith(comparator)
+ }
+ // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+ val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
+ PerUserNameCredentialEntryList(it.key, it.value)
+ }.sortedWith(
+ compareBy(comparator) { it.sortedCredentialEntryList.first() }
+ )
+
+ return ProviderDisplayInfo(
+ sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList,
+ authenticationEntryList = authenticationEntryList,
+ remoteEntry = remoteEntryList.getOrNull(0),
+ )
+}
+
+internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
+ override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
+ // First order by last used timestamp
+ if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
+ if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
+ return 1
+ } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
+ return -1
+ }
+ } else if (p0.lastUsedTimeMillis != null && p0.lastUsedTimeMillis > 0) {
+ return -1
+ } else if (p1.lastUsedTimeMillis != null && p1.lastUsedTimeMillis > 0) {
+ return 1
+ }
+
+ // Then prefer passkey type for its security benefits
+ if (p0.credentialType != p1.credentialType) {
+ if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
+ return -1
+ } else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
+ return 1
+ }
+ }
+ return 0
+ }
+}
\ 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 b427de6..76d9847 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -19,34 +19,93 @@
import android.graphics.drawable.Drawable
data class ProviderInfo(
+ /**
+ * Unique id (component name) of this provider.
+ * Not for display purpose - [displayName] should be used for ui rendering.
+ */
+ val id: String,
val icon: Drawable,
- val name: String,
val displayName: String,
- val credentialTypeIcon: Drawable,
- val credentialOptions: List<CredentialOptionInfo>,
- // TODO: Add the authenticationOption
+ val credentialEntryList: List<CredentialEntryInfo>,
+ val authenticationEntry: AuthenticationEntryInfo?,
+ val remoteEntry: RemoteEntryInfo?,
+ val actionEntryList: List<ActionEntryInfo>,
)
-open class EntryInfo (
+/** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping
+ * by the provider id but instead focuses on structures convenient for display purposes. */
+data class ProviderDisplayInfo(
+ /**
+ * The credential entries grouped by userName, derived from all entries of the [providerInfoList].
+ * Note that the list order matters to the display order.
+ */
+ val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
+ val remoteEntry: RemoteEntryInfo?
+)
+
+abstract class EntryInfo (
+ /** Unique id combination of this entry. Not for display purpose. */
+ val providerId: String,
val entryKey: String,
val entrySubkey: String,
)
-class CredentialOptionInfo(
+class CredentialEntryInfo(
+ providerId: String,
entryKey: String,
entrySubkey: String,
+ /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
+ val credentialType: String,
+ /** Localized type value of this credential used for display purpose. */
+ val credentialTypeDisplayName: String,
+ val userName: String,
+ val displayName: String?,
val icon: Drawable,
- val usageData: String,
-) : EntryInfo(entryKey, entrySubkey)
+ val lastUsedTimeMillis: Long?,
+) : EntryInfo(providerId, entryKey, entrySubkey)
+
+class AuthenticationEntryInfo(
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+ val title: String,
+ val icon: Drawable,
+) : EntryInfo(providerId, entryKey, entrySubkey)
+
+class RemoteEntryInfo(
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+) : EntryInfo(providerId, entryKey, entrySubkey)
+
+class ActionEntryInfo(
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+ val title: String,
+ val icon: Drawable,
+ val subTitle: String?,
+) : EntryInfo(providerId, entryKey, entrySubkey)
data class RequestDisplayInfo(
- val userName: String,
- val displayName: String,
- val type: String,
val appDomainName: String,
)
+/**
+ * @property userName the userName that groups all the entries in this list
+ * @property sortedCredentialEntryList the credential entries associated with the [userName] sorted
+ * by last used timestamps and then by credential types
+ */
+data class PerUserNameCredentialEntryList(
+ val userName: String,
+ val sortedCredentialEntryList: List<CredentialEntryInfo>,
+)
+
/** The name of the current screen. */
enum class GetScreenState {
- CREDENTIAL_SELECTION,
+ /** The primary credential selection page. */
+ PRIMARY_SELECTION,
+ /** The secondary credential selection page, where all sign-in options are listed. */
+ ALL_SIGN_IN_OPTIONS,
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
index 12ab436..dfbcae1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
@@ -17,6 +17,7 @@
package com.android.credentialmanager.jetpack.provider
import android.app.slice.Slice
+import android.credentials.ui.Entry
import android.graphics.drawable.Icon
/**
@@ -24,23 +25,46 @@
*
* TODO: move to jetpack.
*/
-abstract class CredentialEntryUi(
- val credentialTypeIcon: Icon,
- val profileIcon: Icon?,
+class CredentialEntryUi(
+ val credentialType: CharSequence,
+ val credentialTypeDisplayName: CharSequence,
+ val userName: CharSequence,
+ val userDisplayName: CharSequence?,
+ val entryIcon: Icon,
val lastUsedTimeMillis: Long?,
val note: CharSequence?,
) {
companion object {
fun fromSlice(slice: Slice): CredentialEntryUi {
- return when (slice.spec?.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> PasskeyCredentialEntryUi.fromSlice(slice)
- TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntryUi.fromSlice(slice)
- else -> throw IllegalArgumentException("Unexpected type: ${slice.spec?.type}")
- }
- }
+ var credentialType = slice.spec!!.type
+ var credentialTypeDisplayName: CharSequence? = null
+ var userName: CharSequence? = null
+ var userDisplayName: CharSequence? = null
+ var entryIcon: Icon? = null
+ var lastUsedTimeMillis: Long? = null
+ var note: CharSequence? = null
- const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
- "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
- const val TYPE_PASSWORD_CREDENTIAL: String = "androidx.credentials.TYPE_PASSWORD"
+ val items = slice.items
+ items.forEach {
+ if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)) {
+ credentialTypeDisplayName = it.text
+ } else if (it.hasHint(Entry.HINT_USER_NAME)) {
+ userName = it.text
+ } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
+ userDisplayName = it.text
+ } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
+ entryIcon = it.icon
+ } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+ lastUsedTimeMillis = it.long
+ } else if (it.hasHint(Entry.HINT_NOTE)) {
+ note = it.text
+ }
+ }
+
+ return CredentialEntryUi(
+ credentialType, credentialTypeDisplayName!!, userName!!, userDisplayName, entryIcon!!,
+ lastUsedTimeMillis, note,
+ )
+ }
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
deleted file mode 100644
index c5dbe66..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
+++ /dev/null
@@ -1,63 +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.credentialmanager.jetpack.provider
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-class PasskeyCredentialEntryUi(
- val userName: CharSequence,
- val userDisplayName: CharSequence?,
- credentialTypeIcon: Icon,
- profileIcon: Icon?,
- lastUsedTimeMillis: Long?,
- note: CharSequence?,
-) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
- companion object {
- fun fromSlice(slice: Slice): CredentialEntryUi {
- var userName: CharSequence? = null
- var userDisplayName: CharSequence? = null
- var credentialTypeIcon: Icon? = null
- var profileIcon: Icon? = null
- var lastUsedTimeMillis: Long? = null
- var note: CharSequence? = null
-
- val items = slice.items
- items.forEach {
- if (it.hasHint(Entry.HINT_USER_NAME)) {
- userName = it.text
- } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
- userDisplayName = it.text
- } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
- credentialTypeIcon = it.icon
- } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
- profileIcon = it.icon
- } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
- lastUsedTimeMillis = it.long
- } else if (it.hasHint(Entry.HINT_NOTE)) {
- note = it.text
- }
- }
- // TODO: fail NPE more elegantly.
- return PasskeyCredentialEntryUi(
- userName!!, userDisplayName, credentialTypeIcon!!,
- profileIcon, lastUsedTimeMillis, note,
- )
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
deleted file mode 100644
index 5049503..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
+++ /dev/null
@@ -1,68 +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.credentialmanager.jetpack.provider
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a password credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class PasswordCredentialEntryUi(
- val userName: CharSequence,
- val password: CharSequence,
- credentialTypeIcon: Icon,
- profileIcon: Icon?,
- lastUsedTimeMillis: Long?,
- note: CharSequence?,
-) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
- companion object {
- fun fromSlice(slice: Slice): CredentialEntryUi {
- var userName: CharSequence? = null
- var password: CharSequence? = null
- var credentialTypeIcon: Icon? = null
- var profileIcon: Icon? = null
- var lastUsedTimeMillis: Long? = null
- var note: CharSequence? = null
-
- val items = slice.items
- items.forEach {
- if (it.hasHint(Entry.HINT_USER_NAME)) {
- userName = it.text
- } else if (it.hasHint(Entry.HINT_PASSWORD_VALUE)) {
- password = it.text
- } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
- credentialTypeIcon = it.icon
- } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
- profileIcon = it.icon
- } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
- lastUsedTimeMillis = it.long
- } else if (it.hasHint(Entry.HINT_NOTE)) {
- note = it.text
- }
- }
- // TODO: fail NPE more elegantly.
- return PasswordCredentialEntryUi(
- userName!!, password!!, credentialTypeIcon!!,
- profileIcon, lastUsedTimeMillis, note,
- )
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
index b260cf6..bcc0531 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
@@ -26,7 +26,7 @@
* TODO: move to jetpack.
*/
class SaveEntryUi(
- val userProviderAccountName: CharSequence,
+ val userProviderAccountName: CharSequence?,
val credentialTypeIcon: Icon?,
val profileIcon: Icon?,
val passwordCount: Int?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
new file mode 100644
index 0000000..15ae329
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.credentialmanager.ui.theme
+
+import android.annotation.ColorInt
+import android.content.Context
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import com.android.internal.R
+
+/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
+val LocalAndroidColorScheme =
+ staticCompositionLocalOf<AndroidColorScheme> {
+ throw IllegalStateException(
+ "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
+ "Composable surrounded by a CredentialSelectorTheme {}."
+ )
+ }
+
+/**
+ * The Android color scheme.
+ *
+ * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
+ * most of the colors in this class will be removed in favor of their M3 counterpart.
+ */
+class AndroidColorScheme internal constructor(context: Context) {
+
+ val colorPrimary = getColor(context, R.attr.colorPrimary)
+ val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
+ val colorAccent = getColor(context, R.attr.colorAccent)
+ val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
+ val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
+ val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
+ val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
+ val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
+ val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
+ val colorSurface = getColor(context, R.attr.colorSurface)
+ val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
+ val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
+ val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
+ val colorError = getColor(context, R.attr.colorError)
+ val colorBackground = getColor(context, R.attr.colorBackground)
+ val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
+ val panelColorBackground = getColor(context, R.attr.panelColorBackground)
+ val textColorPrimary = getColor(context, R.attr.textColorPrimary)
+ val textColorSecondary = getColor(context, R.attr.textColorSecondary)
+ val textColorTertiary = getColor(context, R.attr.textColorTertiary)
+ val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
+ val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
+ val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
+ val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
+ val colorForeground = getColor(context, R.attr.colorForeground)
+ val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
+
+ private fun getColor(context: Context, attr: Int): Color {
+ val ta = context.obtainStyledAttributes(intArrayOf(attr))
+ @ColorInt val color = ta.getColor(0, 0)
+ ta.recycle()
+ return Color(color)
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
index 5ea6993..d8a8f16 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -6,6 +6,15 @@
val Shapes = Shapes(
small = RoundedCornerShape(100.dp),
- medium = RoundedCornerShape(20.dp),
+ medium = RoundedCornerShape(28.dp),
large = RoundedCornerShape(0.dp)
)
+
+object EntryShape {
+ val TopRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 0.dp, 0.dp)
+ val BottomRoundedCorner = RoundedCornerShape(0.dp, 0.dp, 28.dp, 28.dp)
+ // Used for middle entries.
+ val FullSmallRoundedCorner = RoundedCornerShape(4.dp, 4.dp, 4.dp, 4.dp)
+ // Used for when there's a single entry.
+ val FullMediumRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 28.dp, 28.dp)
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
index 248df92..3ca0e44 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -2,37 +2,37 @@
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
-
-private val AppDarkColorScheme = darkColorScheme(
- primary = Purple200,
- secondary = Purple700,
- tertiary = Teal200
-)
-
-private val AppLightColorScheme = lightColorScheme(
- primary = Purple500,
- secondary = Purple700,
- tertiary = Teal200
-)
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
@Composable
fun CredentialSelectorTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
- val AppColorScheme = if (darkTheme) {
- AppDarkColorScheme
- } else {
- AppLightColorScheme
- }
+ val context = LocalContext.current
+
+ val colorScheme =
+ if (darkTheme) {
+ dynamicDarkColorScheme(context)
+ } else {
+ dynamicLightColorScheme(context)
+ }
+ val androidColorScheme = AndroidColorScheme(context)
+ val typography = Typography
MaterialTheme(
- colorScheme = AppColorScheme,
- typography = Typography,
- shapes = Shapes,
- content = content
- )
+ colorScheme,
+ typography = typography,
+ shapes = Shapes
+ ) {
+ CompositionLocalProvider(
+ LocalAndroidColorScheme provides androidColorScheme,
+ ) {
+ content()
+ }
+ }
}
diff --git a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
index 62dba98..0867293 100644
--- a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
@@ -3,8 +3,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="keyguard_description" msgid="8582605799129954556">"Please enter your password and continue to Dynamic System Updates"</string>
<string name="notification_install_completed" msgid="6252047868415172643">"Dynamic system is ready. To start using it, restart your device."</string>
- <string name="notification_install_inprogress" msgid="7383334330065065017">"Installation in progress"</string>
- <string name="notification_install_failed" msgid="4066039210317521404">"Installation failed"</string>
+ <string name="notification_install_inprogress" msgid="7383334330065065017">"Install in progress"</string>
+ <string name="notification_install_failed" msgid="4066039210317521404">"Install failed"</string>
<string name="notification_image_validation_failed" msgid="2720357826403917016">"Image validation failed. Abort installation."</string>
<string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Currently running a dynamic system. Restart to use the original Android version."</string>
<string name="notification_action_cancel" msgid="5929299408545961077">"Cancel"</string>
diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml
index ab48729..1161783 100644
--- a/packages/InputDevices/res/values-en-rCA/strings.xml
+++ b/packages/InputDevices/res/values-en-rCA/strings.xml
@@ -19,7 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
- <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, phonetic"</string>
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, Phonetic"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml
index a6dcd5d..7460e0a 100644
--- a/packages/PackageInstaller/res/values-af/strings.xml
+++ b/packages/PackageInstaller/res/values-af/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Onbekend"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Jou tablet word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Jou TV word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Jou horlosie word vir jou veiligheid tans nie toegelaat om onbekende apps van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Jou foon word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jou foon en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou foon of verlies van data wat uit sy gebruik kan spruit."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jou tablet en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou tablet of verlies van data wat uit sy gebruik kan spruit."</string>
diff --git a/packages/PackageInstaller/res/values-am/strings.xml b/packages/PackageInstaller/res/values-am/strings.xml
index e58923a..2934b01 100644
--- a/packages/PackageInstaller/res/values-am/strings.xml
+++ b/packages/PackageInstaller/res/values-am/strings.xml
@@ -83,6 +83,8 @@
<string name="app_name_unknown" msgid="6881210203354323926">"ያልታወቀ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ለእርስዎ ደህንነት ሲባል በአሁኑ ጊዜ ጡባዊዎ ከዚህ ምንጭ ያልታወቁ መተግበሪያዎችን እንዲጭን አይፈቀድለትም። ይህን በቅንብሮች ውስጥ መቀየር ይችላሉ።"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ለእርስዎ ደህንነት ሲባል በአሁኑ ጊዜ የእርስዎ ቲቪ ከዚህ ምንጭ ያልታወቁ መተግበሪያዎችን እንዲጭን አይፈቀድለትም። ይህን በቅንብሮች ውስጥ መቀየር ይችላሉ።"</string>
+ <!-- no translation found for untrusted_external_source_warning (7195163388090818636) -->
+ <skip />
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ለእርስዎ ደህንነት ሲባል በአሁኑ ጊዜ ስልክዎ ከዚህ ምንጭ ያልታወቁ መተግበሪያዎችን እንዲጭን አልተፈቀደለትም። ይህን በቅንብሮች ውስጥ መቀየር ይችላሉ።"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"የእርስዎ ስልክ እና የግል ውሂብ በማይታወቁ መተግበሪያዎች ለሚደርሱ ጥቃቶች በይልበልጥ ተጋላጭ ናቸው። ይህን መተግበሪያ በመጫንዎ በእርስዎ ስልክ ላይ ለሚደርስ ማናቸውም ጉዳት ወይም መተግበሪያውን በመጠቀም ለሚከሰት የውሂብ መጥፋት ኃላፊነቱን እንደሚወስዱ ተስማምተዋል።"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"የእርስዎ ጡባዊ እና የግል ውሂብ በማይታወቁ መተግበሪያዎች ለሚደርሱ ጥቃቶች በይበልጥ ተጋላጭ ናቸው። ይህን መተግበሪያ በመጫንዎ በእርስዎ ጡባዊ ላይ ለሚደርስ ማናቸውም ጉዳት ወይም መተግበሪያውን በመጠቀም ለሚከሰት የውሂብ መጥፋት ኃላፊነቱን እንደሚወስዱ ተስማምተዋል።"</string>
diff --git a/packages/PackageInstaller/res/values-ar/strings.xml b/packages/PackageInstaller/res/values-ar/strings.xml
index 1dea809..26b5dae 100644
--- a/packages/PackageInstaller/res/values-ar/strings.xml
+++ b/packages/PackageInstaller/res/values-ar/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"غير معروف"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"لأغراض الأمان، غير مسموح حاليًا لجهازك اللوحي بتثبيت تطبيقات غير معروفة من هذا المصدر. ويمكنك تغيير ذلك في \"الإعدادات\"."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"لأغراض الأمان، غير مسموح حاليًا لجهاز التلفزيون الذي تستخدمه بتثبيت تطبيقات غير معروفة من هذا المصدر. ويمكنك تغيير ذلك في \"الإعدادات\"."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"للحفاظ على أمانك، غير مسموح حاليًا لساعتك بتثبيت تطبيقات غير معروفة من هذا المصدر. يمكنك تغيير ذلك في الإعدادات."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"لأغراض الأمان، غير مسموح حاليًا لهاتفك بتثبيت تطبيقات غير معروفة من هذا المصدر. يمكنك تغيير ذلك في \"الإعدادات\"."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"يعتبر الهاتف والبيانات الشخصية أكثر عرضة لهجوم التطبيقات غير المعروفة. من خلال تثبيت هذا التطبيق، توافق على تحمل مسؤولية أي ضرر يحدث لهاتفك أو فقدان البيانات الذي قد ينتج عن استخدامه."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"يعتبر الجهاز اللوحي والبيانات الشخصية أكثر عرضة لهجوم التطبيقات غير المعروفة. من خلال تثبيت هذا التطبيق، توافق على تحمل مسؤولية أي ضرر يحدث للجهاز اللوحي أو فقدان البيانات الذي قد ينتج عن استخدامه."</string>
diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml
index 8405335..f11fd41 100644
--- a/packages/PackageInstaller/res/values-as/strings.xml
+++ b/packages/PackageInstaller/res/values-as/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"অজ্ঞাত"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টেবলেটটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টিভিটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ ঘড়ীটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ ফ’নটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্টো ইনষ্টল কৰি এপ্টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্টো ইনষ্টল কৰি এপ্টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
diff --git a/packages/PackageInstaller/res/values-az/strings.xml b/packages/PackageInstaller/res/values-az/strings.xml
index e4f8541..9746964 100644
--- a/packages/PackageInstaller/res/values-az/strings.xml
+++ b/packages/PackageInstaller/res/values-az/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Naməlum"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Təhlükəsizliyiniz üçün planşetinizə bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Təhlükəsizliyiniz üçün TV-nizə bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Təhlükəsizliyiniz üçün saatınıza bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Təhlükəsizliyiniz üçün telefonunuza bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Şəxsi məlumatlarınız naməlum mənbə tətbiqlərindən olan hücumlar tərəfindən ələ keçirilə bilər. Bu cür tətbiqləri quraşdırmaqla smartfona dəyəcək bütün zədələrə, məlumatlarınızın oğurlanmasına və itirilməsinə görə məsuliyyəti öz üzərinizə götürdüyünüzü qəbul edirsiniz."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Planşet və şəxsi data naməlum tətbiqlərin hücumuna qarşı daha həssasdır. Bu tətbiqi quraşdırmaqla planşetə dəyə biləcək zərər və ya onun istifadəsi nəticəsində baş verə biləcək data itkisinə görə məsuliyyət daşıdığınızı qəbul edirsiniz."</string>
diff --git a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
index f646b20..c149f80 100644
--- a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
+++ b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Nepoznato"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Iz bezbednosnih razloga tabletu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u podešavanjima."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Iz bezbednosnih razloga televizoru trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u podešavanjima."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Iz bezbednosnih razloga satu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u Podešavanjima."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Iz bezbednosnih razloga telefonu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u podešavanjima."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefon i lični podaci su podložniji napadu nepoznatih aplikacija. Ako instalirate ovu aplikaciju, prihvatate da ste odgovorni za eventualna oštećenja telefona ili gubitak podataka do kojih može da dođe zbog njenog korišćenja."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet i lični podaci su podložniji napadu nepoznatih aplikacija. Ako instalirate ovu aplikaciju, prihvatate da ste odgovorni za eventualna oštećenja tableta ili gubitak podataka do kojih može da dođe zbog njenog korišćenja."</string>
diff --git a/packages/PackageInstaller/res/values-be/strings.xml b/packages/PackageInstaller/res/values-be/strings.xml
index 8a3fd9f..79ea8de 100644
--- a/packages/PackageInstaller/res/values-be/strings.xml
+++ b/packages/PackageInstaller/res/values-be/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Невядома"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"У мэтах бяспекі вашаму планшэту забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Вы можаце змяніць гэты дазвол у Наладах."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"У мэтах бяспекі вашаму тэлевізару забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Вы можаце змяніць гэты дазвол у Наладах."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Дзеля забеспячэння бяспекі вашаму гадзінніку забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Гэтую функцыю можна змяніць у Наладах."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"У мэтах бяспекі вашаму тэлефону забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Вы можаце змяніць гэты дазвол у Наладах."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ваш тэлефон і асабістыя даныя больш прыступныя для атак невядомых праграм. Усталёўваючы гэту праграму, вы згаджаецеся з тым, што несяце адказнасць за любыя пашкоджанні тэлефона ці страту даных, якія могуць адбыцца ў выніку выкарыстання гэтай праграмы."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ваш планшэт і асабістыя даныя больш прыступныя для атак невядомых праграм. Усталёўваючы гэту праграму, вы згаджаецеся з тым, што несяце адказнасць за любыя пашкоджанні планшэта ці страту даных, якія могуць адбыцца ў выніку выкарыстання гэтай праграмы."</string>
diff --git a/packages/PackageInstaller/res/values-bg/strings.xml b/packages/PackageInstaller/res/values-bg/strings.xml
index 9bd36e4..3a75898 100644
--- a/packages/PackageInstaller/res/values-bg/strings.xml
+++ b/packages/PackageInstaller/res/values-bg/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Неизвестно"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"От съображения за сигурност понастоящем на таблета ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"От съображения за сигурност понастоящем на телевизора ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"От съображения за сигурност понастоящем на часовника ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"От съображения за сигурност понастоящем на телефона ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонът и личните ви данни са по-уязвими към атаки от неизвестни приложения. С инсталирането на това приложение приемате, че носите отговорност при евентуална повреда на телефона или загуба на информация вследствие на използването на приложението."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таблетът и личните ви данни са по-уязвими към атаки от неизвестни приложения. С инсталирането на това приложение приемате, че носите отговорност при евентуална повреда на таблета или загуба на информация вследствие на използването на приложението."</string>
diff --git a/packages/PackageInstaller/res/values-bn/strings.xml b/packages/PackageInstaller/res/values-bn/strings.xml
index 0edb6d6..97d0750 100644
--- a/packages/PackageInstaller/res/values-bn/strings.xml
+++ b/packages/PackageInstaller/res/values-bn/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"অজানা"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"আপনার নিরাপত্তার জন্য, এই সোর্স থেকে বর্তমানে আপনার ট্যাবে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"আপনার নিরাপত্তার জন্য, এই সোর্স থেকে বর্তমানে আপনার টিভিতে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"আপনার নিরাপত্তার জন্য, বর্তমানে এই সোর্স থেকে আপনার ওয়াচে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"আপনার নিরাপত্তার জন্য, এই সোর্স থেকে বর্তমানে আপনার ফোনে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"অজানা অ্যাপের দ্বারা আপনার ফোন এবং ব্যক্তিগত ডেটা আক্রান্ত হওয়ার সম্ভাবনা বেশি থাকে। এই অ্যাপটি ইনস্টল করার মাধ্যমে আপনি সম্মত হচ্ছেন যে এটি ব্যবহারের ফলে আপনার ফোনের বা ডেটার কোনও ক্ষতি হলে তার জন্য আপনিই দায়ী থাকবেন।"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"অজানা অ্যাপের দ্বারা আপনার ট্যাবলেট এবং ব্যক্তিগত ডেটা আক্রান্ত হওয়ার সম্ভাবনা বেশি থাকে। এই অ্যাপটি ইনস্টল করার মাধ্যমে আপনি সম্মত হচ্ছেন যে এটি ব্যবহারের ফলে আপনার ট্যাবলেটের বা ডেটার কোনও ক্ষতি হলে তার জন্য আপনিই দায়ী থাকবেন।"</string>
diff --git a/packages/PackageInstaller/res/values-bs/strings.xml b/packages/PackageInstaller/res/values-bs/strings.xml
index edd82bc..d724c20 100644
--- a/packages/PackageInstaller/res/values-bs/strings.xml
+++ b/packages/PackageInstaller/res/values-bs/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Nepoznato"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Radi vaše sigurnosti tabletu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Radi vaše sigurnosti TV-u trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Radi vaše sigurnosti, satu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Radi vaše sigurnosti telefonu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Vaši podaci na telefonu i vaši lični podaci izloženiji su napadima nepoznatih aplikacija. Instaliranjem ove aplikacije, prihvatate odgovornost za bilo kakvu štetu na telefonu ili gubitak podataka do kojih može doći korištenjem aplikacije."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Vaši podaci na tabletu i vaši lični podaci izloženiji su napadima nepoznatih aplikacija. Instaliranjem ove aplikacije, prihvatate odgovornost za bilo kakvu štetu na tabletu ili gubitak podataka do kojih može doći korištenjem aplikacije."</string>
diff --git a/packages/PackageInstaller/res/values-ca/strings.xml b/packages/PackageInstaller/res/values-ca/strings.xml
index 577ae27..880bad6 100644
--- a/packages/PackageInstaller/res/values-ca/strings.xml
+++ b/packages/PackageInstaller/res/values-ca/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Desconeguda"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Per la teva seguretat, actualment la tauleta no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Per la teva seguretat, actualment la televisió no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Per a la teva seguretat, actualment el rellotge no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Per la teva seguretat, actualment el telèfon no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"El telèfon i les dades personals són més vulnerables als atacs d\'aplicacions desconegudes. En instal·lar aquesta aplicació, acceptes que ets responsable de qualsevol dany que es produeixi al telèfon o de la pèrdua de dades que pugui resultar del seu ús."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"La tauleta i les dades personals són més vulnerables als atacs d\'aplicacions desconegudes. En instal·lar aquesta aplicació, acceptes que ets responsable de qualsevol dany que es produeixi a la tauleta o de la pèrdua de dades que pugui resultar del seu ús."</string>
diff --git a/packages/PackageInstaller/res/values-cs/strings.xml b/packages/PackageInstaller/res/values-cs/strings.xml
index bd18421..11f903a 100644
--- a/packages/PackageInstaller/res/values-cs/strings.xml
+++ b/packages/PackageInstaller/res/values-cs/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Neznámé"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Z bezpečnostních důvodů momentálně není dovoleno do tabletu instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Z bezpečnostních důvodů momentálně není dovoleno do televize instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Z bezpečnostních důvodů momentálně není dovoleno do hodinek instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Z bezpečnostních důvodů momentálně není dovoleno do telefonu instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefon a osobní údaje jsou zranitelnější vůči útoku ze strany neznámých aplikací. Instalací této aplikace přijímáte odpovědnost za případné škody na telefonu nebo ztrátu dat, která může být používáním aplikace způsobena."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet a osobní údaje jsou zranitelnější vůči útoku ze strany neznámých aplikací. Instalací této aplikace přijímáte odpovědnost za případné škody na tabletu nebo ztrátu dat, která může být používáním aplikace způsobena."</string>
diff --git a/packages/PackageInstaller/res/values-da/strings.xml b/packages/PackageInstaller/res/values-da/strings.xml
index 32355ca..6feba42 100644
--- a/packages/PackageInstaller/res/values-da/strings.xml
+++ b/packages/PackageInstaller/res/values-da/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Ukendt"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Af hensyn til din sikkerhed har din tablet i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Af hensyn til din sikkerhed har dit fjernsyn i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Af hensyn til din sikkerhed har dit ur i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Af hensyn til din sikkerhed har din telefon i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Din telefon og dine personlige data er mere sårbare over for angreb fra ukendte apps. Når du installerer denne app, accepterer du, at du er ansvarlig for skader på din telefon eller tab af data, der kan skyldes brug af appen."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Din tablet og dine personlige data er mere sårbare over for angreb fra ukendte apps. Når du installerer denne app, accepterer du, at du er ansvarlig for skader på din tablet eller tab af data, der kan skyldes brug af appen."</string>
diff --git a/packages/PackageInstaller/res/values-de/strings.xml b/packages/PackageInstaller/res/values-de/strings.xml
index 6dc81d0..0537b63 100644
--- a/packages/PackageInstaller/res/values-de/strings.xml
+++ b/packages/PackageInstaller/res/values-de/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Unbekannt"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Aus Sicherheitsgründen kannst du auf deinem Tablet zurzeit keine unbekannten Apps aus dieser Quelle installieren. Das kannst du in den Einstellungen ändern."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Aus Sicherheitsgründen kannst du auf deinem Fernseher zurzeit keine unbekannten Apps aus dieser Quelle installieren. Das kannst du in den Einstellungen ändern."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Aus Sicherheitsgründen kannst du auf deiner Smartwatch zurzeit keine unbekannten Apps aus dieser Quelle installieren. Du kannst das in den Einstellungen ändern."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Aus Sicherheitsgründen kannst du auf deinem Smartphone zurzeit keine unbekannten Apps aus dieser Quelle installieren. Das kannst du in den Einstellungen ändern."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Unbekannte Apps können gefährlich für dein Smartphone und deine personenbezogenen Daten sein. Wenn du diese App installierst, erklärst du dich damit einverstanden, dass du die Verantwortung für alle Schäden an deinem Smartphone und jegliche Datenverluste trägst, die aus der Verwendung dieser App entstehen können."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Unbekannte Apps können gefährlich für dein Tablet und deine personenbezogenen Daten sein. Wenn du diese App installierst, erklärst du dich damit einverstanden, dass du die Verantwortung für alle Schäden an deinem Tablet und jegliche Datenverluste trägst, die aus der Verwendung dieser App entstehen können."</string>
diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml
index c67f6f7..fa053fc 100644
--- a/packages/PackageInstaller/res/values-el/strings.xml
+++ b/packages/PackageInstaller/res/values-el/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Άγνωστη"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στο tablet σας. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στην τηλεόρασή σας. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στο ρολόι σας. Αυτό μπορείτε να το αλλάξετε από την ενότητα Ρυθμίσεις."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στο τηλέφωνό σας. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Το τηλέφωνό σας και τα προσωπικά δεδομένα σας είναι πιο ευάλωτα σε επιθέσεις από άγνωστες εφαρμογές. Με την εγκατάσταση αυτής της εφαρμογής, συμφωνείτε ότι είστε υπεύθυνοι για τυχόν βλάβη που μπορεί να προκληθεί στο τηλέφωνο ή απώλεια δεδομένων που μπορεί να προκύψει από τη χρήση τους."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Το tablet σας και τα προσωπικά δεδομένα σας είναι πιο ευάλωτα σε επιθέσεις από άγνωστες εφαρμογές. Με την εγκατάσταση αυτής της εφαρμογής, συμφωνείτε ότι είστε υπεύθυνοι για τυχόν βλάβη που μπορεί να προκληθεί στο tablet ή απώλεια δεδομένων που μπορεί να προκύψει από τη χρήση τους."</string>
diff --git a/packages/PackageInstaller/res/values-en-rAU/strings.xml b/packages/PackageInstaller/res/values-en-rAU/strings.xml
index f09f7bb..828ac73 100644
--- a/packages/PackageInstaller/res/values-en-rAU/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rAU/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
diff --git a/packages/PackageInstaller/res/values-en-rCA/strings.xml b/packages/PackageInstaller/res/values-en-rCA/strings.xml
index f09f7bb..1ad309c 100644
--- a/packages/PackageInstaller/res/values-en-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rCA/strings.xml
@@ -60,13 +60,13 @@
<string name="uninstall_update_text" msgid="863648314632448705">"Replace this app with the factory version? All data will be removed."</string>
<string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles."</string>
<string name="uninstall_keep_data" msgid="7002379587465487550">"Keep <xliff:g id="SIZE">%1$s</xliff:g> of app data."</string>
- <string name="uninstalling_notification_channel" msgid="840153394325714653">"Running uninstallations"</string>
- <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Failed uninstallations"</string>
+ <string name="uninstalling_notification_channel" msgid="840153394325714653">"Running uninstalls"</string>
+ <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Failed uninstalls"</string>
<string name="uninstalling" msgid="8709566347688966845">"Uninstalling…"</string>
<string name="uninstalling_app" msgid="8866082646836981397">"Uninstalling <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
- <string name="uninstall_done" msgid="439354138387969269">"Uninstallation finished."</string>
+ <string name="uninstall_done" msgid="439354138387969269">"Uninstall finished."</string>
<string name="uninstall_done_app" msgid="4588850984473605768">"Uninstalled <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
- <string name="uninstall_failed" msgid="1847750968168364332">"Uninstallation unsuccessful."</string>
+ <string name="uninstall_failed" msgid="1847750968168364332">"Uninstall unsuccessful."</string>
<string name="uninstall_failed_app" msgid="5506028705017601412">"Uninstalling <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> unsuccessful."</string>
<string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Can\'t uninstall active device admin app"</string>
<string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Can\'t uninstall active device admin app for <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
@@ -76,21 +76,22 @@
<string name="manage_device_administrators" msgid="3092696419363842816">"Manage device admin apps"</string>
<string name="manage_users" msgid="1243995386982560813">"Manage users"</string>
<string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> couldn\'t be uninstalled."</string>
- <string name="Parse_error_dlg_text" msgid="1661404001063076789">"There was a problem while parsing the package."</string>
+ <string name="Parse_error_dlg_text" msgid="1661404001063076789">"There was a problem parsing the package."</string>
<string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string>
- <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Install/uninstall actions not supported on Wear."</string>
+ <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Install/Uninstall actions not supported on Wear."</string>
<string name="message_staging" msgid="8032722385658438567">"Staging app…"</string>
<string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
<string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Your TV and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your TV or loss of data that may result from its use."</string>
<string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string>
- <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling Wear apps"</string>
+ <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling wear apps"</string>
<string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string>
<string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string>
- <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed \'<xliff:g id="APPNAME">%1$s</xliff:g>\'"</string>
+ <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed “<xliff:g id="APPNAME">%1$s</xliff:g>”"</string>
</resources>
diff --git a/packages/PackageInstaller/res/values-en-rGB/strings.xml b/packages/PackageInstaller/res/values-en-rGB/strings.xml
index f09f7bb..828ac73 100644
--- a/packages/PackageInstaller/res/values-en-rGB/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rGB/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
diff --git a/packages/PackageInstaller/res/values-en-rIN/strings.xml b/packages/PackageInstaller/res/values-en-rIN/strings.xml
index f09f7bb..828ac73 100644
--- a/packages/PackageInstaller/res/values-en-rIN/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rIN/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
diff --git a/packages/PackageInstaller/res/values-en-rXC/strings.xml b/packages/PackageInstaller/res/values-en-rXC/strings.xml
index fddc164..3e09e89 100644
--- a/packages/PackageInstaller/res/values-en-rXC/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rXC/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
diff --git a/packages/PackageInstaller/res/values-es-rUS/strings.xml b/packages/PackageInstaller/res/values-es-rUS/strings.xml
index a0fff45..cd5cfc3 100644
--- a/packages/PackageInstaller/res/values-es-rUS/strings.xml
+++ b/packages/PackageInstaller/res/values-es-rUS/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Desconocido"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Por tu seguridad, la tablet no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Por tu seguridad, la TV no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Por tu seguridad, el reloj no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Por tu seguridad, el teléfono no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"El teléfono y tus datos personales son más vulnerables a los ataques de apps desconocidas. Si instalas esta app, serás responsable de los daños que sufra el teléfono y de la pérdida de datos que pueda ocasionar su uso."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"La tablet y tus datos personales son más vulnerables a los ataques de apps desconocidas. Si instalas esta app, serás responsable de los daños que sufra la tablet y de la pérdida de datos que pueda ocasionar su uso."</string>
diff --git a/packages/PackageInstaller/res/values-es/strings.xml b/packages/PackageInstaller/res/values-es/strings.xml
index 37599c4..9fb37e1 100644
--- a/packages/PackageInstaller/res/values-es/strings.xml
+++ b/packages/PackageInstaller/res/values-es/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Desconocida"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Por tu seguridad, de momento tu tablet no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Ajustes."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Por tu seguridad, de momento tu televisor no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Ajustes."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Por motivos de seguridad, de momento tu reloj no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Configuración."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Por tu seguridad, de momento tu teléfono no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Ajustes."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Tu teléfono y tus datos personales son más vulnerables a los ataques de aplicaciones desconocidas. Al instalar esta aplicación, aceptas ser responsable de cualquier daño que sufra tu teléfono o la pérdida de datos que se pueda derivar de su uso."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tu tablet y tus datos personales son más vulnerables a los ataques de aplicaciones desconocidas. Al instalar esta aplicación, aceptas ser responsable de cualquier daño que sufra tu tablet o la pérdida de datos que se pueda derivar de su uso."</string>
diff --git a/packages/PackageInstaller/res/values-et/strings.xml b/packages/PackageInstaller/res/values-et/strings.xml
index 2324806..e7dedac 100644
--- a/packages/PackageInstaller/res/values-et/strings.xml
+++ b/packages/PackageInstaller/res/values-et/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Teadmata"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Teie turvalisuse huvides ei ole tahvelarvutil praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Teie turvalisuse huvides ei ole teleril praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Teie turvalisuse huvides ei ole kellal praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Teie turvalisuse huvides ei ole telefonil praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Teie telefon ja isiklikud andmed on tundmatute rakenduste rünnakute suhtes haavatavamad. Selle rakenduse installimisel nõustute, et vastutate telefoni kahjude ja andmekao eest, mis võivad tuleneda selliste rakenduste kasutamisest."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Teie tahvelarvuti ja isiklikud andmed on tundmatute rakenduste rünnakute suhtes haavatavamad. Selle rakenduse installimisel nõustute, et vastutate tahvelarvuti kahjude ja andmekao eest, mis võivad tuleneda selliste rakenduste kasutamisest."</string>
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index fe6edce..5b8b0ee 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Ezezaguna"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak tableta honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telebista honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak erloju honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telefono honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Baliteke telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jasatea. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telefonoari agian gertatuko zaizkion kalteen edo datu-galeren erantzulea."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Baliteke tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jasatea. Aplikazio hau instalatzen baduzu, onartu egingo duzu hura erabiltzeagatik tabletari agian gertatuko zaizkion kalteen edo datu-galeren erantzulea zeu izango zarela."</string>
diff --git a/packages/PackageInstaller/res/values-fa/strings.xml b/packages/PackageInstaller/res/values-fa/strings.xml
index b05a087..38c2c22 100644
--- a/packages/PackageInstaller/res/values-fa/strings.xml
+++ b/packages/PackageInstaller/res/values-fa/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"نامشخص"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"برای امنیت شما، درحالحاضر رایانه لوحیتان اجازه ندارد برنامههای ناشناس را از این منبع نصب کنید. میتوانید آن را در «تنظیمات» تغییر دهید."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"برای امنیت شما، درحالحاضر تلویزیونتان اجازه ندارد برنامههای ناشناس را از این منبع نصب کنید. میتوانید آن را در «تنظیمات» تغییر دهید."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"برای امنیت شما، درحالحاضر ساعتتان اجازه ندارد برنامههای ناشناس را از این منبع نصب کنید. میتوانید این مورد را در «تنظیمات» تغییر دهید."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"برای امنیت شما، درحالحاضر تلفنتان اجازه ندارد برنامههای ناشناس را از این منبع نصب کنید. میتوانید آن را در «تنظیمات» تغییر دهید."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"تلفن و دادههای شخصیتان دربرابر حمله برنامههای ناشناس آسیبپذیرتر هستند. با نصب این برنامه، موافقت میکنید که مسئول هرگونه آسیب به تلفن یا از دست رفتن دادهای هستید که ممکن است درنتیجه استفاده از آن به وجود آید."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"رایانه لوحی و دادههای شخصیتان دربرابر حمله برنامههای ناشناس آسیبپذیرتر هستند. با نصب این برنامه، موافقت میکنید که مسئول هرگونه آسیب به رایانه لوحی یا از دست رفتن دادهای هستید که ممکن است درنتیجه استفاده از آن به وجود آید."</string>
diff --git a/packages/PackageInstaller/res/values-fi/strings.xml b/packages/PackageInstaller/res/values-fi/strings.xml
index a8048e2..3958ed2 100644
--- a/packages/PackageInstaller/res/values-fi/strings.xml
+++ b/packages/PackageInstaller/res/values-fi/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Tuntematon"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Turvallisuussyistä tablettisi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Turvallisuussyistä televisiosi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Turvallisuussyistä kellosi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Turvallisuussyistä puhelimesi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Tuntemattomat sovellukset voivat helpommin kaapata puhelimesi ja henkilökohtaiset tietosi. Lataamalla sovelluksia tästä lähteestä hyväksyt, että olet itse vastuussa puhelimellesi aiheutuvista vahingoista tai tietojen menetyksestä, jotka voivat johtua sovellusten käytöstä."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tuntemattomat sovellukset voivat helpommin kaapata tablettisi ja henkilökohtaiset tietosi. Lataamalla sovelluksia tästä lähteestä hyväksyt, että olet itse vastuussa tabletillesi aiheutuvista vahingoista tai tietojen menetyksestä, jotka voivat johtua sovellusten käytöstä."</string>
diff --git a/packages/PackageInstaller/res/values-fr-rCA/strings.xml b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
index d11336f..9e22fa4 100644
--- a/packages/PackageInstaller/res/values-fr-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Inconnue"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur cette tablette. Vous pouvez modifier cette option dans les paramètres."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur ce téléviseur. Vous pouvez modifier cette option dans les paramètres."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur cette montre. Vous pouvez modifier cette option dans les paramètres."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur ce téléphone. Vous pouvez modifier cette option dans les paramètres."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données personnelles sont plus vulnérables aux attaques provenant d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de l\'utilisation de telles applications."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données personnelles sont plus vulnérables aux attaques provenant d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de l\'utilisation de telles applications."</string>
diff --git a/packages/PackageInstaller/res/values-fr/strings.xml b/packages/PackageInstaller/res/values-fr/strings.xml
index a02851e..0275233d 100644
--- a/packages/PackageInstaller/res/values-fr/strings.xml
+++ b/packages/PackageInstaller/res/values-fr/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Inconnu"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur cette tablette actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur ce téléviseur actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur cette montre actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur ce téléphone actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
diff --git a/packages/PackageInstaller/res/values-gl/strings.xml b/packages/PackageInstaller/res/values-gl/strings.xml
index a6454da..05aec90 100644
--- a/packages/PackageInstaller/res/values-gl/strings.xml
+++ b/packages/PackageInstaller/res/values-gl/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Nome descoñecido"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Por cuestións de seguranza, na tableta non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Por cuestións de seguranza, na televisión non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Por cuestións de seguranza, no reloxo non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Por cuestións de seguranza, no teléfono non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"O teléfono e os datos persoais son máis vulnerables aos ataques de aplicacións descoñecidas. Ao instalar esta aplicación, aceptas que es responsable dos danos ocasionados no teléfono ou da perda dos datos que se poidan derivar do seu uso."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"A tableta e os datos persoais son máis vulnerables aos ataques de aplicacións descoñecidas. Ao instalar esta aplicación, aceptas que es responsable dos danos ocasionados na tableta ou da perda dos datos que se poidan derivar do seu uso."</string>
diff --git a/packages/PackageInstaller/res/values-gu/strings.xml b/packages/PackageInstaller/res/values-gu/strings.xml
index 7851d3d..e109360 100644
--- a/packages/PackageInstaller/res/values-gu/strings.xml
+++ b/packages/PackageInstaller/res/values-gu/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"અજાણ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"તમારી સુરક્ષા માટે, હાલમાં તમારા ટૅબ્લેટને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે આને સેટિંગમાં બદલી શકો છો."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"તમારી સુરક્ષા માટે, હાલમાં તમારા ટીવીને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે આને સેટિંગમાં બદલી શકો છો."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"તમારી સુરક્ષા માટે, હાલમાં તમારી વૉચને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે સેટિંગમાં જઈને આને બદલી શકો છો."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"તમારી સુરક્ષા માટે, હાલમાં તમારા ફોનને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે આને સેટિંગમાં બદલી શકો છો."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"તમારો ફોન અને વ્યક્તિગત ડેટા અજાણી ઍપ્લિકેશનો દ્વારા હુમલા માટે વધુ સંવેદનશીલ છે. આ ઍપ્લિકેશન ઇન્સ્ટૉલ કરીને તમે સંમત થાઓ છો કે આનો ઉપયોગ કરવાથી તમારા ફોનને થતી કોઈપણ હાનિ અથવા ડેટાના નુકસાન માટે તમે જવાબદાર છો."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"તમારું ટૅબ્લેટ અને વ્યક્તિગત ડેટા અજાણી ઍપ્લિકેશનો દ્વારા હુમલા માટે વધુ સંવેદનશીલ છે. આ ઍપ્લિકેશન ઇન્સ્ટૉલ કરીને તમે સંમત થાઓ છો કે આનો ઉપયોગ કરવાથી તમારા ટૅબ્લેટને થતી કોઈપણ હાનિ અથવા ડેટાના નુકસાન માટે તમે જવાબદાર છો."</string>
diff --git a/packages/PackageInstaller/res/values-hi/strings.xml b/packages/PackageInstaller/res/values-hi/strings.xml
index c6a1f40..702034ed 100644
--- a/packages/PackageInstaller/res/values-hi/strings.xml
+++ b/packages/PackageInstaller/res/values-hi/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"अनजान"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"आपकी सुरक्षा के लिए, आपके टैबलेट को फ़िलहाल इस स्रोत से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. आप \'सेटिंग\' में जाकर इसे बदल सकते हैं."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"आपकी सुरक्षा के लिए, आपके टीवी को फ़िलहाल इस स्रोत से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. आप \'सेटिंग\' में जाकर इसे बदल सकते हैं."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"आपकी सुरक्षा के लिए, फ़िलहाल स्मार्टवॉच को इस सोर्स से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. \'सेटिंग\' में जाकर इसे बदला जा सकता है."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"आपकी सुरक्षा के लिए, आपके फ़ोन को फ़िलहाल इस सोर्स से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. \'सेटिंग\' में जाकर इसे बदला जा सकता है."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"आपका फ़ोन और निजी डेटा अनजान ऐप्लिकेशन के हमले को लेकर ज़्यादा संवेदनशील हैं. इस ऐप्लिकेशन को इंस्टॉल करके, आप सहमति देते हैं कि इसके इस्तेमाल के चलते आपके फ़ोन को होने वाले किसी भी नुकसान या डेटा के मिट जाने पर, आप ज़िम्मेदार होंगे."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"आपका टैबलेट और निजी डेटा अनजान ऐप्लिकेशन के हमले को लेकर ज़्यादा संवेदनशील हैं. इस ऐप्लिकेशन को इंस्टॉल करके, आप सहमति देते हैं कि इसके इस्तेमाल के चलते आपके टैबलेट को होने वाले किसी भी नुकसान या डेटा के मिट जाने पर, आप ज़िम्मेदार होंगे."</string>
diff --git a/packages/PackageInstaller/res/values-hr/strings.xml b/packages/PackageInstaller/res/values-hr/strings.xml
index 707eb4e..cccf998 100644
--- a/packages/PackageInstaller/res/values-hr/strings.xml
+++ b/packages/PackageInstaller/res/values-hr/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Nepoznato"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Iz sigurnosnih razloga tablet trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u Postavkama."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Iz sigurnosnih razloga televizor trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u Postavkama."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Iz sigurnosnih razloga sat trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u postavkama."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Iz sigurnosnih razloga telefon trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u Postavkama."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Vaš telefon i osobni podaci podložniji su napadima nepoznatih aplikacija. Instaliranjem te aplikacije prihvaćate odgovornost za oštećenje telefona ili gubitak podataka do kojih može doći uslijed njezine upotrebe."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Vaš tablet i osobni podaci podložniji su napadima nepoznatih aplikacija. Instaliranjem te aplikacije prihvaćate odgovornost za oštećenje tableta ili gubitak podataka do kojih može doći uslijed njezine upotrebe."</string>
diff --git a/packages/PackageInstaller/res/values-hu/strings.xml b/packages/PackageInstaller/res/values-hu/strings.xml
index 70ebadb..3b55307 100644
--- a/packages/PackageInstaller/res/values-hu/strings.xml
+++ b/packages/PackageInstaller/res/values-hu/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Ismeretlen"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Az Ön biztonsága érdekében táblagépe jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt módosíthatja a Beállítások között."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Az Ön biztonsága érdekében tévéje jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt módosíthatja a Beállítások között."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Az Ön biztonsága érdekében órája jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt a Beállítások között módosíthatja."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Az Ön biztonsága érdekében telefonja jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt módosíthatja a Beállítások között."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonja és személyes adatai fokozott kockázatnak vannak kitéve az ismeretlen alkalmazások támadásaival szemben. Az alkalmazás telepítésével elfogadja, hogy Ön a felelős az alkalmazás használatából eredő esetleges adatvesztésért és a telefont ért károkért."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Táblagépe és személyes adatai fokozott kockázatnak vannak kitéve az ismeretlen alkalmazások támadásaival szemben. Az alkalmazás telepítésével elfogadja, hogy Ön a felelős az alkalmazás használatából eredő esetleges adatvesztésért és a táblagépet ért károkért."</string>
diff --git a/packages/PackageInstaller/res/values-hy/strings.xml b/packages/PackageInstaller/res/values-hy/strings.xml
index 288c9b1..77b56d4 100644
--- a/packages/PackageInstaller/res/values-hy/strings.xml
+++ b/packages/PackageInstaller/res/values-hy/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Անհայտ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Անվտանգության նկատառումներից ելնելով՝ ձեր պլանշետում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից: Սա կարող եք փոխել կարգավորումներում։"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Անվտանգության նկատառումներից ելնելով՝ ձեր հեռուստացույցում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից: Սա կարող եք փոխել կարգավորումներում։"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Անվտանգության նկատառումներից ելնելով՝ ձեր ժամացույցում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից։ Սա կարող եք փոխել կարգավորումներում։"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Անվտանգության նկատառումներից ելնելով՝ ձեր հեռախոսում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից: Սա կարող եք փոխել կարգավորումներում։"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ձեր հեռախոսը և անձնական տվյալներն առավել խոցելի են անհայտ հավելվածների գրոհների նկատմամբ: Տեղադրելով այս հավելվածը՝ դուք ընդունում եք, որ պատասխանատվություն եք կրում հավելվածի օգտագործման հետևանքով ձեր հեռախոսին հասցված ցանկացած վնասի կամ տվյալների կորստի համար:"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ձեր պլանշետը և անձնական տվյալներն առավել խոցելի են անհայտ հավելվածների գրոհների նկատմամբ: Տեղադրելով այս հավելվածը՝ դուք ընդունում եք, որ պատասխանատվություն եք կրում հավելվածի օգտագործման հետևանքով ձեր պլանշետին հասցված ցանկացած վնասի կամ տվյալների կորստի համար:"</string>
diff --git a/packages/PackageInstaller/res/values-in/strings.xml b/packages/PackageInstaller/res/values-in/strings.xml
index e3e5606..d49df3e 100644
--- a/packages/PackageInstaller/res/values-in/strings.xml
+++ b/packages/PackageInstaller/res/values-in/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Tidak dikenal"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Demi keamanan, tablet Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Demi keamanan, TV Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Demi keamanan, smartwatch Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Demi keamanan, ponsel Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ponsel dan data pribadi Anda lebih rentan terhadap serangan oleh aplikasi yang tidak dikenal. Dengan menginstal aplikasi ini, Anda setuju bahwa Anda bertanggung jawab atas kerusakan ponsel atau kehilangan data yang mungkin diakibatkan oleh penggunaannya."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet dan data pribadi Anda lebih rentan terhadap serangan oleh aplikasi yang tidak dikenal. Dengan menginstal aplikasi ini, Anda setuju bahwa Anda bertanggung jawab atas kerusakan tablet atau kehilangan data yang mungkin diakibatkan oleh penggunaannya."</string>
diff --git a/packages/PackageInstaller/res/values-is/strings.xml b/packages/PackageInstaller/res/values-is/strings.xml
index 7f0ee04..138ecc7 100644
--- a/packages/PackageInstaller/res/values-is/strings.xml
+++ b/packages/PackageInstaller/res/values-is/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Óþekkt"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í spjaldtölvunni þinni. Þú getur breytt þessu í stillingum."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í sjónvarpinu þínu. Þú getur breytt þessu í stillingum."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í úrinu þínu. Þú getur breytt þessu í stillingunum."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í símanum þínum. Þú getur breytt þessu í stillingum."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Síminn þinn og persónuleg gögn eru berskjaldaðri fyrir árásum forrita af óþekktum uppruna. Með uppsetningu þessa forrits samþykkirðu að bera fulla ábyrgð á hverju því tjóni sem verða kann á símanum eða gagnatapi sem leiða kann af notkun þess."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Spjaldtölvan þín og persónuleg gögn eru berskjaldaðri fyrir árásum forrita af óþekktum uppruna. Með uppsetningu þessa forrits samþykkirðu að bera fulla ábyrgð á hverju því tjóni sem verða kann á spjaldtölvunni eða gagnatapi sem leiða kann af notkun þess."</string>
diff --git a/packages/PackageInstaller/res/values-it/strings.xml b/packages/PackageInstaller/res/values-it/strings.xml
index 3fe7ba4..e57e319 100644
--- a/packages/PackageInstaller/res/values-it/strings.xml
+++ b/packages/PackageInstaller/res/values-it/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Sconosciuto"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Per motivi di sicurezza, il tuo tablet non è attualmente autorizzato a installare app sconosciute da questa origine. Puoi modificare questa opzione nelle Impostazioni."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Per motivi di sicurezza, la tua TV non è attualmente autorizzata a installare app sconosciute da questa origine. Puoi modificare questa opzione nelle Impostazioni."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Per motivi di sicurezza, il tuo smartwatch non è attualmente autorizzato a installare app sconosciute da questa origine. Puoi modificare questa preferenza nelle Impostazioni."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Per motivi di sicurezza, il tuo telefono non è attualmente autorizzato a installare app sconosciute da questa origine. Puoi modificare questa opzione nelle Impostazioni."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"I dati del telefono e i dati personali sono più vulnerabili agli attacchi di app sconosciute. Se installi questa app, accetti di essere responsabile degli eventuali danni al telefono o dell\'eventuale perdita di dati derivanti dall\'uso dell\'app."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"I dati del tablet e i dati personali sono più vulnerabili agli attacchi di app sconosciute. Se installi questa app, accetti di essere responsabile degli eventuali danni al tablet o dell\'eventuale perdita di dati derivanti dall\'uso dell\'app."</string>
diff --git a/packages/PackageInstaller/res/values-iw/strings.xml b/packages/PackageInstaller/res/values-iw/strings.xml
index 2d7c988..c923b98 100644
--- a/packages/PackageInstaller/res/values-iw/strings.xml
+++ b/packages/PackageInstaller/res/values-iw/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"לא ידוע"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"לצורכי אבטחה, הטאבלט שלך חסום להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"לצורכי אבטחה, הטלוויזיה שלך חסומה להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"לצורכי אבטחה, השעון שלך חסום להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"לצורכי אבטחה, הטלפון שלך חסום להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"נתוני הטלפון והנתונים האישיים שלך חשופים יותר בפני התקפות על ידי אפליקציות ממקורות לא ידועים. התקנת האפליקציה הזו מהווה את הסכמתך לכך שהאחריות הבלעדית היא שלך במקרה של אובדן נתונים או גרימת נזק לטלפון שלך בעקבות השימוש באפליקציה."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"נתוני הטאבלט והנתונים האישיים שלך חשופים יותר בפני התקפות על ידי אפליקציות ממקורות לא ידועים. התקנת האפליקציה הזו מהווה את הסכמתך לכך שהאחריות הבלעדית היא שלך במקרה של אובדן נתונים או גרימת נזק לטאבלט בעקבות השימוש באפליקציה."</string>
diff --git a/packages/PackageInstaller/res/values-ja/strings.xml b/packages/PackageInstaller/res/values-ja/strings.xml
index e2a5aaa..bc6f917 100644
--- a/packages/PackageInstaller/res/values-ja/strings.xml
+++ b/packages/PackageInstaller/res/values-ja/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"不明"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"セキュリティ上の理由から、お使いのタブレットでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"セキュリティ上の理由から、お使いのテレビでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"セキュリティ上の理由から、お使いのウォッチでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"セキュリティ上の理由から、お使いのスマートフォンでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"不明なアプリをインストールするとスマートフォンや個人データの侵害に対する安全性が低下します。このアプリをインストールすることで、アプリの使用により生じる可能性があるスマートフォンへの侵害やデータの損失について、ユーザーご自身が単独で責任を負うことに同意することになります。"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"不明なアプリをインストールするとタブレットや個人データの侵害に対する安全性が低下します。このアプリをインストールすることで、アプリの使用により生じる可能性があるタブレットへの侵害やデータの損失について、ユーザーご自身が単独で責任を負うことに同意することになります。"</string>
diff --git a/packages/PackageInstaller/res/values-ka/strings.xml b/packages/PackageInstaller/res/values-ka/strings.xml
index ea6d45e..507aafb 100644
--- a/packages/PackageInstaller/res/values-ka/strings.xml
+++ b/packages/PackageInstaller/res/values-ka/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"უცნობი"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"თქვენივე უსაფრთხოებისთვის თქვენს ტაბლეტს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის ნებართვა. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"თქვენივე უსაფრთხოებისთვის თქვენს ტელეფონს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის ნებართვა. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"თქვენივე უსაფრთხოებისთვის თქვენს საათს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის ნებართვა. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"თქვენივე უსაფრთხოებისთვის, ტელეფონს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის უფლება. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"თქვენი ტელეფონი და პერსონალური მონაცემები მეტად დაუცველია უცნობი აპების მხრიდან შეტევების წინაშე. ამ აპის ინსტალაციის შემთხვევაში, თქვენ თანახმა ხართ, პასუხისმგებელი იყოთ მისი გამოყენების შედეგად ტელეფონისთვის მიყენებულ ზიანსა თუ მონაცემების დაკარგვაზე."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"თქვენი ტაბლეტი და პერსონალური მონაცემები მეტად დაუცველია უცნობი აპების მხრიდან შეტევების წინაშე. ამ აპის ინსტალაციის შემთხვევაში, თქვენ თანახმა ხართ, პასუხისმგებელი იყოთ მისი გამოყენების შედეგად ტაბლეტისთვის მიყენებულ ზიანსა თუ მონაცემების დაკარგვაზე."</string>
diff --git a/packages/PackageInstaller/res/values-kk/strings.xml b/packages/PackageInstaller/res/values-kk/strings.xml
index 20eed5b..15eeb4d 100644
--- a/packages/PackageInstaller/res/values-kk/strings.xml
+++ b/packages/PackageInstaller/res/values-kk/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Белгісіз"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Қауіпсіздік үшін планшетке бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Қауіпсіздік үшін теледидарға бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Қауіпсіздік үшін cағатқа бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Қауіпсіздік үшін телефонға бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефон және жеке деректер белгісіз қолданбалардың шабуылына ұшырауы мүмкін. Бұл қолданбаны орнату арқылы оны пайдалану нәтижесіндегі телефонға келетін залалға немесе деректердің жоғалуына өзіңіз ғана жауапты болатыныңызға келісесіз."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Планшет және жеке деректер белгісіз қолданбалардың шабуылына ұшырауы мүмкін. Бұл қолданбаны орнату арқылы оны пайдалану нәтижесіндегі планшетке келетін залалға немесе деректердің жоғалуына өзіңіз ғана жауапты болатыныңызға келісесіз."</string>
diff --git a/packages/PackageInstaller/res/values-km/strings.xml b/packages/PackageInstaller/res/values-km/strings.xml
index 06c5ea2..bec78b0 100644
--- a/packages/PackageInstaller/res/values-km/strings.xml
+++ b/packages/PackageInstaller/res/values-km/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"មិនស្គាល់"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្នទូរទស្សន៍របស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាចប្ដូរវាបាននៅក្នុងការកំណត់។"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្នទូរទស្សន៍របស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាចប្ដូរវាបាននៅក្នុងការកំណត់។"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្ននាឡិការបស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាចផ្លាស់ប្ដូរវាបាននៅក្នុងការកំណត់។"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្នទូរសព្ទរបស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាចប្ដូរវាបាននៅក្នុងការកំណត់។"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ទូរសព្ទ និងទិន្នន័យផ្ទាល់ខ្លួនរបស់អ្នកកាន់តែងាយនឹងរងគ្រោះពីការវាយប្រហារពីកម្មវិធីដែលមិនស្គាល់។ ប្រសិនបើដំឡើងកម្មវិធីនេះ អ្នកយល់ព្រមថា អ្នកទទួលខុសត្រូវលើការខូចខាតទាំងឡាយមកលើទូរសព្ទរបស់អ្នក ឬការបាត់បង់ទិន្នន័យ ដែលអាចបណ្ដាលមកពីការប្រើប្រាស់កម្មវិធីនេះ។"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ថេប្លេត និងទិន្នន័យផ្ទាល់ខ្លួនរបស់អ្នកងាយនឹងរងគ្រោះពីការវាយប្រហារពីកម្មវិធីដែលមិនស្គាល់។ ប្រសិនបើដំឡើងកម្មវិធីនេះ មានន័យថាអ្នកទទួលខុសត្រូវលើការខូចខាតទាំងឡាយចំពោះថេប្លេត ឬការបាត់បង់ទិន្នន័យរបស់អ្នក ដែលអាចបណ្ដាលមកពីការប្រើប្រាស់កម្មវិធីនេះ។"</string>
diff --git a/packages/PackageInstaller/res/values-kn/strings.xml b/packages/PackageInstaller/res/values-kn/strings.xml
index 4c6b2ff..29722c2 100644
--- a/packages/PackageInstaller/res/values-kn/strings.xml
+++ b/packages/PackageInstaller/res/values-kn/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"ಅಪರಿಚಿತ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್ಗಳನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್ಗಳನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ಟಿವಿಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್ಗಳನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ವಾಚ್ಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್ಗಳನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ಫೋನ್ಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ನಿಮ್ಮ ಫೋನ್ ಹಾಗೂ ವೈಯಕ್ತಿಕ ಡೇಟಾ, ಅಪರಿಚಿತ ಆ್ಯಪ್ಗಳ ದಾಳಿಗೆ ತುತ್ತಾಗುವ ಸಾಧ್ಯತೆ ಹೆಚ್ಚಾಗಿದೆ. ಈ ಆ್ಯಪ್ ಅನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್ಗೆ ಯಾವುದೇ ಹಾನಿ ಉಂಟಾದರೆ ಅಥವಾ ಅದರ ಬಳಕೆಯಿಂದ ಡೇಟಾ ನಷ್ಟವಾದರೆ, ಅದಕ್ಕೆ ನೀವೇ ಜವಾಬ್ದಾರರು ಎನ್ನುವುದನ್ನು ಒಪ್ಪಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ ಹಾಗೂ ವೈಯಕ್ತಿಕ ಡೇಟಾ, ಅಪರಿಚಿತ ಆ್ಯಪ್ಗಳ ದಾಳಿಗೆ ತುತ್ತಾಗುವ ಸಾಧ್ಯತೆ ಹೆಚ್ಚಾಗಿದೆ. ಈ ಆ್ಯಪ್ ಅನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್ಗೆ ಯಾವುದೇ ಹಾನಿ ಉಂಟಾದರೆ ಅಥವಾ ಅದರ ಬಳಕೆಯಿಂದ ಡೇಟಾ ನಷ್ಟವಾದರೆ, ಅದಕ್ಕೆ ನೀವೇ ಜವಾಬ್ದಾರರು ಎನ್ನುವುದನ್ನು ಒಪ್ಪಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
diff --git a/packages/PackageInstaller/res/values-ko/strings.xml b/packages/PackageInstaller/res/values-ko/strings.xml
index b1e00a4..dfe2d84 100644
--- a/packages/PackageInstaller/res/values-ko/strings.xml
+++ b/packages/PackageInstaller/res/values-ko/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"알 수 없음"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"보안상의 이유로 이 출처의 알 수 없는 앱을 태블릿에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"보안상의 이유로 현재 이 출처의 알 수 없는 앱을 TV에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"보안상의 이유로 현재 이 출처의 알 수 없는 앱을 시계에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"보안상의 이유로 현재 이 출처의 알 수 없는 앱을 휴대전화에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"휴대전화와 개인 데이터는 알 수 없는 앱의 공격에 더욱 취약합니다. 이 앱을 설치하면 앱 사용으로 인해 발생할 수 있는 모든 휴대전화 손상이나 데이터 손실에 사용자가 책임을 진다는 것에 동의하게 됩니다."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"태블릿과 개인 데이터는 알 수 없는 앱의 공격에 더욱 취약합니다. 이 앱을 설치하면 앱 사용으로 인해 발생할 수 있는 모든 태블릿 손상이나 데이터 손실에 사용자가 책임을 진다는 것에 동의하게 됩니다."</string>
diff --git a/packages/PackageInstaller/res/values-ky/strings.xml b/packages/PackageInstaller/res/values-ky/strings.xml
index 2684fd9..f543955 100644
--- a/packages/PackageInstaller/res/values-ky/strings.xml
+++ b/packages/PackageInstaller/res/values-ky/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Белгисиз"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Коопсуздук максатында, планшетиңизге бул булактан колдонмолорду орнотууга болбойт. Бул параметрди каалаган убакта жөндөөлөрдөн өзгөртө аласыз."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Коопсуздук максатында, сыналгыңызга бул булактан колдонмолорду орнотууга болбойт. Бул параметрди каалаган убакта жөндөөлөрдөн өзгөртө аласыз."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Коопсуздукту сактоо максатында, азырынча саатыңызда бул булактан белгисиз колдонмолорду орнотууга уруксат жок. Муну параметрлерден өзгөртсөңүз болот."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Коопсуздук максатында, телефонуңузга бул булактан колдонмолорду орнотууга болбойт. Бул параметрди каалаган убакта жөндөөлөрдөн өзгөртө аласыз."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонуңуз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам телефонуңузга кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Планшетиңиз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам планшетиңизге кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
diff --git a/packages/PackageInstaller/res/values-lo/strings.xml b/packages/PackageInstaller/res/values-lo/strings.xml
index 868c35d..58df433 100644
--- a/packages/PackageInstaller/res/values-lo/strings.xml
+++ b/packages/PackageInstaller/res/values-lo/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"ບໍ່ຮູ້ຈັກ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ຕອນນີ້ແທັບເລັດຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້ໄດ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ຕອນນີ້ໂທລະທັດຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້ໄດ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ປັດຈຸບັນໂມງຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ຕອນນີ້ໂທລະສັບຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້ໄດ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ໂທລະສັບ ແລະ ຂໍ້ມູນສ່ວນຕົວຂອງທ່ານອາດຖືກໂຈມຕີໄດ້ໂດຍແອັບທີ່ບໍ່ຮູ້ຈັກ. ໂດຍການຕິດຕັ້ງແອັບນີ້, ແມ່ນທ່ານຍອມຮັບວ່າທ່ານຈະຮັບຜິດຊອບຕໍ່ຄວາມເສຍຫາຍໃດໆກໍຕາມທີ່ເກີດຂຶ້ນຕໍ່ໂທລະທັດຂອງທ່ານ ຫຼື ການສູນເສຍຂໍ້ມູນທີ່ອາດເກີດຈາກການນຳໃຊ້ມັນ."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ແທັບເລັດ ແລະ ຂໍ້ມູນສ່ວນຕົວຂອງທ່ານອາດຖືກໂຈມຕີໄດ້ໂດຍແອັບທີ່ບໍ່ຮູ້ຈັກ. ໂດຍການຕິດຕັ້ງແອັບນີ້, ແມ່ນທ່ານຍອມຮັບວ່າທ່ານຈະຮັບຜິດຊອບຕໍ່ຄວາມເສຍຫາຍໃດໆກໍຕາມທີ່ເກີດຂຶ້ນຕໍ່ໂທລະທັດຂອງທ່ານ ຫຼື ການສູນເສຍຂໍ້ມູນທີ່ອາດເກີດຈາກການນຳໃຊ້ມັນ."</string>
diff --git a/packages/PackageInstaller/res/values-lt/strings.xml b/packages/PackageInstaller/res/values-lt/strings.xml
index 4dfbb27..e1e4e64 100644
--- a/packages/PackageInstaller/res/values-lt/strings.xml
+++ b/packages/PackageInstaller/res/values-lt/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Nežinoma"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Saugos sumetimais šiuo metu planšetiniame kompiuteryje neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Saugos sumetimais šiuo metu televizoriuje neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Saugos sumetimais šiuo metu laikrodyje neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Saugos sumetimais šiuo metu telefone neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonas ir asmens duomenys labiau pažeidžiami įdiegus nežinomų programų. Įdiegdami šią programą sutinkate, kad patys esate atsakingi už žalą telefonui arba prarastus duomenis dėl šios programos naudojimo."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Planšetinis kompiuteris ir asmens duomenys labiau pažeidžiami įdiegus nežinomų programų. Įdiegdami šią programą sutinkate, kad patys esate atsakingi už žalą planšetiniam kompiuteriui arba prarastus duomenis dėl šios programos naudojimo."</string>
diff --git a/packages/PackageInstaller/res/values-lv/strings.xml b/packages/PackageInstaller/res/values-lv/strings.xml
index 2a5507f..82d3013 100644
--- a/packages/PackageInstaller/res/values-lv/strings.xml
+++ b/packages/PackageInstaller/res/values-lv/strings.xml
@@ -83,6 +83,8 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Nezināma"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Drošības apsvērumu dēļ jūsu planšetdatorā pašlaik nav atļauts instalēt nezināmas lietotnes no šī avota. Jūs varat to mainīt iestatījumos."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Drošības apsvērumu dēļ jūsu televizorā pašlaik nav atļauts instalēt nezināmas lietotnes no šī avota. Jūs varat to mainīt iestatījumos."</string>
+ <!-- no translation found for untrusted_external_source_warning (7195163388090818636) -->
+ <skip />
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Drošības apsvērumu dēļ jūsu tālrunī pašlaik nav atļauts instalēt nezināmas lietotnes no šī avota. Jūs varat to mainīt iestatījumos."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jūsu tālrunis un personas dati ir neaizsargātāki pret uzbrukumiem no nezināmām lietotnēm. Instalējot šo lietotni, jūs piekrītat, ka esat atbildīgs par tālruņa bojājumiem vai datu zudumu, kas var rasties lietotnes dēļ."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jūsu planšetdators un personas dati ir neaizsargātāki pret uzbrukumiem no nezināmām lietotnēm. Instalējot šo lietotni, jūs piekrītat, ka esat atbildīgs par planšetdatora bojājumiem vai datu zudumu, kas var rasties lietotnes dēļ."</string>
diff --git a/packages/PackageInstaller/res/values-mk/strings.xml b/packages/PackageInstaller/res/values-mk/strings.xml
index 93f37bf..18e4432 100644
--- a/packages/PackageInstaller/res/values-mk/strings.xml
+++ b/packages/PackageInstaller/res/values-mk/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Непозната"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"За ваша безбедност, таблетот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"За ваша безбедност, телевизорот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"За ваша безбедност, часовникот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"За ваша безбедност, телефонот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонот и личните податоци се поподложни на напади од апликации од непознати извори. Ако ја инсталирате апликацијава, се согласувате дека сте одговорни за каква било штета на телефонот или загуба на податоци поради нејзиното користење."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таблетот и личните податоци се поподложни на напади од апликации од непознати извори. Ако ја инсталирате апликацијава, се согласувате дека сте одговорни за каква било штета на таблетот или загуба на податоци поради нејзиното користење."</string>
diff --git a/packages/PackageInstaller/res/values-ml/strings.xml b/packages/PackageInstaller/res/values-ml/strings.xml
index 66de3f1..99c6b2a 100644
--- a/packages/PackageInstaller/res/values-ml/strings.xml
+++ b/packages/PackageInstaller/res/values-ml/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"അജ്ഞാതം"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"നിങ്ങളുടെ സുരക്ഷയ്ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ ടാബ്ലെറ്റിന് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"നിങ്ങളുടെ സുരക്ഷയ്ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ ടിവിക്ക് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"നിങ്ങളുടെ സുരക്ഷയ്ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ വാച്ചിന് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"നിങ്ങളുടെ സുരക്ഷയ്ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ ഫോണിന് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"അജ്ഞാതമായ ആപ്പുകളാൽ നിങ്ങളുടെ ഫോണും വ്യക്തിഗത ഡാറ്റയും ആക്രമിക്കപ്പെടാനുള്ള സാധ്യത വളരെ കൂടുതലാണ്. ഈ ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യുന്നതിലൂടെ, ഇത് ഉപയോഗിക്കുന്നതിനാൽ നിങ്ങളുടെ ഫോണിന് സംഭവിച്ചേക്കാവുന്ന ഏത് നാശനഷ്ടത്തിന്റെയും അല്ലെങ്കിൽ ഡാറ്റാ നഷ്ടത്തിന്റെയും ഉത്തരവാദിത്തം നിങ്ങൾക്കായിരിക്കുമെന്ന് അംഗീകരിക്കുന്നു."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"അജ്ഞാതമായ ആപ്പുകളാൽ നിങ്ങളുടെ ടാബ്ലെറ്റും വ്യക്തിഗത ഡാറ്റയും ആക്രമിക്കപ്പെടാനുള്ള സാധ്യത വളരെ കൂടുതലാണ്. ഈ ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യുന്നതിലൂടെ, ഇത് ഉപയോഗിക്കുന്നതിനാൽ നിങ്ങളുടെ ടാബ്ലെറ്റിന് സംഭവിച്ചേക്കാവുന്ന ഏത് നാശനഷ്ടത്തിന്റെയും അല്ലെങ്കിൽ ഡാറ്റാ നഷ്ടത്തിന്റെയും ഉത്തരവാദിത്തം നിങ്ങൾക്കായിരിക്കുമെന്ന് അംഗീകരിക്കുന്നു."</string>
diff --git a/packages/PackageInstaller/res/values-mn/strings.xml b/packages/PackageInstaller/res/values-mn/strings.xml
index 5eb0e6c..7b2e1d4 100644
--- a/packages/PackageInstaller/res/values-mn/strings.xml
+++ b/packages/PackageInstaller/res/values-mn/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Тодорхойгүй"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Таны аюулгүй байдлыг хангах үүднээс таны таблетыг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Таны аюулгүй байдлыг хангах үүднээс таны ТВ-ийг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Таны аюулгүй байдлыг хангах үүднээс таны цагийг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Таны аюулгүй байдлыг хангах үүднээс таны утсыг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Таны утас болон хувийн өгөгдөл тодорхойгүй апп суулгасан тохиолдолд гэмтэж болзошгүй. Энэ аппыг суулгаснаар үүнийг ашигласнаас үүдэн таны утсанд гэмтэл гарах, эсвэл өгөгдөл устах зэрэг эрсдэлийг хариуцна гэдгээ зөвшөөрч байна."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таны таблет болон хувийн өгөгдөл тодорхойгүй апп суулгасан тохиолдолд гэмтэж болзошгүй. Энэ аппыг суулгаснаар үүнийг ашигласнаас үүдэн таны таблетад гэмтэл гарах, эсвэл өгөгдөл устах зэрэг эрсдэлийг хариуцна гэдгээ зөвшөөрч байна."</string>
diff --git a/packages/PackageInstaller/res/values-mr/strings.xml b/packages/PackageInstaller/res/values-mr/strings.xml
index 40bb680..bf23e7a 100644
--- a/packages/PackageInstaller/res/values-mr/strings.xml
+++ b/packages/PackageInstaller/res/values-mr/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"अज्ञात"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"तुमच्या सुरक्षिततेसाठी, तुमच्या टॅबलेटला सध्या या स्रोतावरील अज्ञात अॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्ज मध्ये बदलू शकता."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"तुमच्या सुरक्षिततेसाठी, तुमच्या टीव्हीला सध्या या स्रोतावरील अज्ञात अॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्ज मध्ये बदलू शकता."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"तुमच्या सुरक्षिततेसाठी, तुमच्या वॉचला सध्या या स्रोतावरील अज्ञात अॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्ज मध्ये बदलू शकता."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"तुमच्या सुरक्षेसाठी, तुमच्या फोनला सध्या या स्रोतावरील अज्ञात अॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्ज मध्ये बदलू शकता."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"तुमचा फोन आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने होणार्या तुमच्या फोनचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"तुमचा टॅबलेट आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने तुमच्या टॅबलेटचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string>
diff --git a/packages/PackageInstaller/res/values-ms/strings.xml b/packages/PackageInstaller/res/values-ms/strings.xml
index 09ed820..15685c1 100644
--- a/packages/PackageInstaller/res/values-ms/strings.xml
+++ b/packages/PackageInstaller/res/values-ms/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Tidak diketahui"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Untuk keselamatan, tablet anda kini tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Untuk keselamatan, TV anda tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Untuk keselamatan anda, jam tangan anda tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Untuk keselamatan anda, telefon anda kini tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefon dan data peribadi anda lebih mudah diserang oleh apl yang tidak diketahui. Dengan memasang apl ini, anda bersetuju bahawa anda bertanggungjawab atas sebarang kerosakan pada telefon anda atau kehilangan data yang mungkin disebabkan oleh penggunaan apl tersebut."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet dan data peribadi anda lebih mudah diserang oleh apl yang tidak diketahui. Dengan memasang apl ini, anda bersetuju bahawa anda bertanggungjawab atas sebarang kerosakan pada tablet anda atau kehilangan data yang mungkin disebabkan oleh penggunaan apl tersebut."</string>
diff --git a/packages/PackageInstaller/res/values-my/strings.xml b/packages/PackageInstaller/res/values-my/strings.xml
index 7b632d0..1c35eb2 100644
--- a/packages/PackageInstaller/res/values-my/strings.xml
+++ b/packages/PackageInstaller/res/values-my/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"အမည်မသိ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"လုံခြုံရေးအရ ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို သင့်တက်ဘလက်တွင် လောလောဆယ် ထည့်သွင်းခွင့်မရှိပါ။ ၎င်းကို ‘ဆက်တင်များ’ တွင် ပြောင်းနိုင်ပါသည်။"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"လုံခြုံရေးအရ ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို သင့် TV တွင် လောလောဆယ် ထည့်သွင်းခွင့်မရှိပါ။ ၎င်းကို ‘ဆက်တင်များ’ တွင် ပြောင်းနိုင်ပါသည်။"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို လုံခြုံရေးအရ သင့်လက်ပတ်နာရီတွင် လောလောဆယ် ထည့်သွင်းခွင့်မပြုပါ။ ၎င်းကို ဆက်တင်များတွင် ပြောင်းနိုင်သည်။"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို သင့်လုံခြုံရေးအတွက် သင့်ဖုန်းတွင် လောလောဆယ် ထည့်သွင်းခွင့်ပြုမထားပါ။ ၎င်းကို ‘ဆက်တင်များ’ တွင် ပြောင်းနိုင်ပါသည်။"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"သင်၏ဖုန်းနှင့် ကိုယ်ရေးကိုယ်တာ အချက်အလက်များသည် အမျိုးအမည် မသိသောအက်ပ်များ၏ တိုက်ခိုက်ခြင်းကို ပိုမိုခံရနိုင်ပါသည်။ ဤအက်ပ်ကို ထည့်သွင်းအသုံးပြုခြင်းအားဖြင့် ဖြစ်ပေါ်လာနိုင်သော ဖုန်းပျက်စီးမှု သို့မဟုတ် ဒေတာဆုံးရှုံးမှုများအတွက် သင့်ထံ၌သာ တာဝန်ရှိကြောင်း သဘောတူရာရောက်ပါသည်။"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"သင်၏ တက်ဘလက်နှင့် ကိုယ်ရေးကိုယ်တာ အချက်အလက်များသည် အမျိုးအမည် မသိသောအက်ပ်များ၏ တိုက်ခိုက်ခြင်းကို ပိုမိုခံရနိုင်ပါသည်။ ဤအက်ပ်ကို ထည့်သွင်းအသုံးပြုခြင်းအားဖြင့် ဖြစ်ပေါ်လာနိုင်သော တက်ဘလက်ပျက်စီးမှု သို့မဟုတ် ဒေတာဆုံးရှုံးမှုများအတွက် သင့်ထံ၌သာ တာဝန်ရှိကြောင်း သဘောတူရာရောက်ပါသည်။"</string>
diff --git a/packages/PackageInstaller/res/values-nb/strings.xml b/packages/PackageInstaller/res/values-nb/strings.xml
index a0a00b3..4d4bc8b 100644
--- a/packages/PackageInstaller/res/values-nb/strings.xml
+++ b/packages/PackageInstaller/res/values-nb/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Ukjent"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Nettbrettet har for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden, for å ivareta sikkerheten din. Du kan endre dette i innstillingene."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"TV-en har for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden, for å ivareta sikkerheten din. Du kan endre dette i innstillingene."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Av sikkerhetsgrunner har klokken for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden. Du kan endre dette i innstillingene."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Telefonen har for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden, for å ivareta sikkerheten din. Du kan endre dette i innstillingene."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonen din og de personlige dataene dine er mer sårbare for angrep fra ukjente apper. Når du installerer denne appen, samtykker du i at du er ansvarlig for eventuelle skader på telefonen eller tap av data som kan skyldes bruk av appen"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Nettbrettet ditt og de personlige dataene dine er mer sårbare for angrep fra ukjente apper. Når du installerer denne appen, samtykker du i at du er ansvarlig for eventuelle skader på nettbrettet eller tap av data som kan skyldes bruk av appen."</string>
diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml
index 50422d7..40e9382 100644
--- a/packages/PackageInstaller/res/values-ne/strings.xml
+++ b/packages/PackageInstaller/res/values-ne/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"अज्ञात"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"तपाईंको सुरक्षार्थ हाल तपाईंको ट्याब्लेटमा यो स्रोतबाट अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"तपाईंको सुरक्षार्थ हाल तपाईंको टिभीमा यो स्रोतबाट अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"तपाईंको सुरक्षाका लागि हाल तपाईंको स्मार्टवाचमा यो स्रोतबाट उपलब्ध अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो अनुमति बदल्न सक्नुहुन्छ।"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"तपाईंको सुरक्षार्थ हाल तपाईंको फोनमा यो स्रोतबाट अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"तपाईंको फोन तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको फोनमा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"तपाईंको ट्याब्लेट तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको ट्याब्लेटमा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string>
diff --git a/packages/PackageInstaller/res/values-nl/strings.xml b/packages/PackageInstaller/res/values-nl/strings.xml
index 53a9a00..9b19458 100644
--- a/packages/PackageInstaller/res/values-nl/strings.xml
+++ b/packages/PackageInstaller/res/values-nl/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Onbekend"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Uit veiligheidsoverwegingen heeft je tablet momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Uit veiligheidsoverwegingen heeft je tv momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Uit veiligheidsoverwegingen heeft je smartwatch momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Uit veiligheidsoverwegingen heeft je telefoon momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Je telefoon en persoonsgegevens zijn kwetsbaarder voor aanvallen door onbekende apps. Als je deze app installeert, ga je ermee akkoord dat je verantwoordelijk bent voor eventuele schade aan je telefoon of gegevensverlies als gevolg van het gebruik van de app."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Je tablet en persoonsgegevens zijn kwetsbaarder voor aanvallen door onbekende apps. Als je deze app installeert, ga je ermee akkoord dat je verantwoordelijk bent voor eventuele schade aan je tablet of gegevensverlies als gevolg van het gebruik van de app."</string>
diff --git a/packages/PackageInstaller/res/values-or/strings.xml b/packages/PackageInstaller/res/values-or/strings.xml
index 75d5d2d..9f3b255 100644
--- a/packages/PackageInstaller/res/values-or/strings.xml
+++ b/packages/PackageInstaller/res/values-or/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"ଅଜଣା"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଟାବଲେଟକୁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପଗୁଡ଼ିକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଟିଭିକୁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପଗୁଡ଼ିକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଅଜଣା ଆପ୍ସକୁ ଇନଷ୍ଟଲ କରିବା ପାଇଁ ଆପଣଙ୍କ ୱାଚକୁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଫୋନକୁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପଗୁଡ଼ିକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ଅଜଣା ଆପ୍ ଦ୍ୱାରା ଆପଣଙ୍କ ଫୋନ୍ ଏବଂ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ନଷ୍ଟ କରାଯାଇପାରିବାର ସମ୍ଭାବନା ବହୁତ ଅଧିକ। ଏହି ଆପ୍କୁ ଇନଷ୍ଟଲ୍ କରିବାର ଅର୍ଥ ହେଉଛି ଆପଣଙ୍କ ଫୋନ୍ରେ ଘଟିବା କୌଣସି ପ୍ରକାର କ୍ଷତି କିମ୍ବା ସେଗୁଡ଼ିକର ବ୍ୟବହାରରୁ ହେବା କୌଣସି ପ୍ରକାର ଡାଟାର ହାନୀ ପାଇଁ ଆପଣ ଦାୟୀ ରହିବାକୁ ରାଜି ହୁଅନ୍ତି।"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ଅଜଣା ଆପ୍ ଦ୍ୱାରା ଆପଣଙ୍କ ଟାବଲେଟ୍ ଏବଂ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ନଷ୍ଟ କରାଯାଇପାରିବାର ସମ୍ଭାବନା ବହୁତ ଅଧିକ। ଏହି ଆପ୍କୁ ଇନଷ୍ଟଲ୍ କରିବାର ଅର୍ଥ ହେଉଛି ଆପଣଙ୍କ ଟାବ୍ଲେଟ୍ରେ ଘଟିବା କୌଣସି ପ୍ରକାର କ୍ଷତି କିମ୍ବା ସେଗୁଡ଼ିକର ବ୍ୟବହାରରୁ ହେବା କୌଣସି ପ୍ରକାର ଡାଟାର ହାନୀ ପାଇଁ ଆପଣ ଦାୟୀ ରହିବାକୁ ରାଜି ହୁଅନ୍ତି।"</string>
diff --git a/packages/PackageInstaller/res/values-pa/strings.xml b/packages/PackageInstaller/res/values-pa/strings.xml
index 05bf127..058af82 100644
--- a/packages/PackageInstaller/res/values-pa/strings.xml
+++ b/packages/PackageInstaller/res/values-pa/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"ਅਗਿਆਤ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ ਟੀਵੀ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੀ ਘੜੀ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ ਫ਼ੋਨ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ਅਗਿਆਤ ਐਪਾਂ ਤੋਂ ਹੋਣ ਵਾਲੇ ਹਮਲਿਆਂ ਕਰਕੇ ਤੁਹਾਡੇ ਫ਼ੋਨ ਅਤੇ ਨਿੱਜੀ ਡਾਟੇ ਨਾਲ ਛੇੜਛਾੜ ਹੋ ਸਕਦੀ ਹੈ। ਇਹ ਐਪ ਸਥਾਪਤ ਕਰਕੇ, ਤੁਸੀਂ ਸਹਿਮਤੀ ਦਿੰਦੇ ਹੋ ਕਿ ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਹੋਣ ਵਾਲੇ ਕਿਸੇ ਵੀ ਨੁਕਸਾਨ ਜਾਂ ਡਾਟੇ ਦੀ ਹਾਨੀ ਲਈ ਤੁਸੀਂ ਜ਼ਿੰਮੇਵਾਰ ਹੋ ਜੋ ਸ਼ਾਇਦ ਇਸ ਐਪ ਨੂੰ ਵਰਤਣ ਦੇ ਨਤੀਜੇ ਵਜੋਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ਤੁਹਾਡਾ ਟੈਬਲੈੱਟ ਅਤੇ ਨਿੱਜੀ ਡਾਟਾ ਅਗਿਆਤ ਐਪਾਂ ਤੋਂ ਹਮਲੇ ਪ੍ਰਤੀ ਵਧੇਰੇ ਵਿੰਨਣਸ਼ੀਲ ਹਨ। ਇਹ ਐਪ ਸਥਾਪਤ ਕਰਕੇ, ਤੁਸੀਂ ਸਹਿਮਤੀ ਦਿੰਦੇ ਹੋ ਕਿ ਆਪਣੇ ਟੈਬਲੈੱਟ ਨੂੰ ਹੋਣ ਵਾਲੇ ਕਿਸੇ ਵੀ ਨੁਕਸਾਨ ਜਾਂ ਡਾਟੇ ਦੀ ਹਾਨੀ ਲਈ ਤੁਸੀਂ ਜ਼ੁੰਮੇਵਾਰ ਹੋ ਜੋ ਸ਼ਾਇਦ ਇਸ ਐਪ ਨੂੰ ਵਰਤਣ ਦੇ ਨਤੀਜੇ ਵਜੋਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
diff --git a/packages/PackageInstaller/res/values-pl/strings.xml b/packages/PackageInstaller/res/values-pl/strings.xml
index 6f6469d..39be12f 100644
--- a/packages/PackageInstaller/res/values-pl/strings.xml
+++ b/packages/PackageInstaller/res/values-pl/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Inny"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Ze względów bezpieczeństwa na Twoim tablecie nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Ze względów bezpieczeństwa na Twoim telewizorze nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Ze względów bezpieczeństwa na Twoim zegarku nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Ze względów bezpieczeństwa na Twoim telefonie nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Dane na telefonie i prywatne są bardziej narażone na atak nieznanych aplikacji. Instalując tę aplikację, bierzesz na siebie odpowiedzialność za ewentualne uszkodzenie telefonu lub utratę danych w wyniku jej używania."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Dane na tablecie i prywatne są bardziej narażone na atak nieznanych aplikacji. Instalując tę aplikację, bierzesz na siebie odpowiedzialność za ewentualne uszkodzenie tabletu lub utratę danych w wyniku jej używania."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rBR/strings.xml b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
index b9e5be8..97a3eb8 100644
--- a/packages/PackageInstaller/res/values-pt-rBR/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Desconhecido"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sua segurança, o tablet não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sua segurança, a TV não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sua segurança, o relógio não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso em \"Configurações\"."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sua segurança, o smartphone não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Seu smartphone e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Seu tablet e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rPT/strings.xml b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
index ae69ed1..ce23ec6 100644
--- a/packages/PackageInstaller/res/values-pt-rPT/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Desconhecida"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sua segurança, o tablet não está atualmente autorizado a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sua segurança, a TV não está atualmente autorizada a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sua segurança, o relógio não está atualmente autorizado a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sua segurança, o telemóvel não está atualmente autorizado a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"O seu telemóvel e os dados pessoais estão mais vulneráveis a ataques por parte de aplicações desconhecidas. Ao instalar esta app, concorda que é responsável por quaisquer danos causados ao telemóvel ou pelas perdas de dados que possam resultar da utilização da mesma."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"O seu tablet e os dados pessoais estão mais vulneráveis a ataques por parte de aplicações desconhecidas. Ao instalar esta app, concorda que é responsável por quaisquer danos causados ao tablet ou pelas perdas de dados que possam resultar da utilização da mesma."</string>
diff --git a/packages/PackageInstaller/res/values-pt/strings.xml b/packages/PackageInstaller/res/values-pt/strings.xml
index b9e5be8..97a3eb8 100644
--- a/packages/PackageInstaller/res/values-pt/strings.xml
+++ b/packages/PackageInstaller/res/values-pt/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Desconhecido"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sua segurança, o tablet não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sua segurança, a TV não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sua segurança, o relógio não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso em \"Configurações\"."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sua segurança, o smartphone não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Seu smartphone e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Seu tablet e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml
index 9ca4543..df99a09 100644
--- a/packages/PackageInstaller/res/values-ro/strings.xml
+++ b/packages/PackageInstaller/res/values-ro/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Necunoscut"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Din motive de securitate, tableta nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Din motive de securitate, televizorul nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Din motive de securitate, ceasul tău nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți stabili altfel din Setări."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Din motive de securitate, telefonul nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonul și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi această aplicație, accepți că ești singura persoană responsabilă pentru deteriorarea telefonului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableta și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi aplicația, accepți că ești singura persoană responsabilă pentru deteriorarea tabletei sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
diff --git a/packages/PackageInstaller/res/values-ru/strings.xml b/packages/PackageInstaller/res/values-ru/strings.xml
index e0cc4ab..8573752 100644
--- a/packages/PackageInstaller/res/values-ru/strings.xml
+++ b/packages/PackageInstaller/res/values-ru/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Неизвестное приложение"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"В целях безопасности ваш планшет блокирует установку неизвестных приложений из этого источника. Этот параметр можно изменить в настройках."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"В целях безопасности ваш телевизор блокирует установку неизвестных приложений из этого источника. Этот параметр можно изменить в настройках."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"В целях безопасности ваши часы блокируют установку неизвестных приложений из этого источника. Изменить параметры можно в настройках."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"В целях безопасности ваш телефон блокирует установку неизвестных приложений из этого источника. Этот параметр можно изменить в настройках."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ваши персональные данные и данные телефона более уязвимы для атак приложений из неизвестных источников. Устанавливая это приложение, вы берете на себя всю ответственность за последствия, связанные с его использованием, то есть за любой ущерб, нанесенный телефону, и возможную потерю данных."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ваши персональные данные и данные планшета более уязвимы для атак приложений из неизвестных источников. Устанавливая это приложение, вы берете на себя всю ответственность за последствия, связанные с его использованием, то есть за любой ущерб, нанесенный планшету, и возможную потерю данных."</string>
diff --git a/packages/PackageInstaller/res/values-si/strings.xml b/packages/PackageInstaller/res/values-si/strings.xml
index fb2344e..5b56298 100644
--- a/packages/PackageInstaller/res/values-si/strings.xml
+++ b/packages/PackageInstaller/res/values-si/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"නොදනී"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ඔබගේ ආරක්ෂාව සඳහා, ඔබගේ ටැබ්ලටයට දැනට මෙම මූලාශ්රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදේ. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැකිය."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ඔබගේ ආරක්ෂාව සඳහා, ඔබගේ TV හට දැනට මෙම මූලාශ්රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදේ. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැකිය."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ඔබේ ආරක්ෂාව සඳහා, ඔබේ ඔරලෝසුවට දැනට මෙම මූලාශ්රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදෙයි. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැක."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ඔබගේ ආරක්ෂාව සඳහා, ඔබගේ දුරකථනයට දැනට මෙම මූලාශ්රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදේ. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැකිය."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ඔබගේ දුරකථනය සහ පුද්ගලික දත්තවලට නොදන්නා යෙදුම් මඟින් තර්ජන එල්ල කිරීමේ හැකියාව වැඩිය. මෙම යෙදුම් ස්ථාපනය කිරීමෙන් සහ භාවිත කිරීමෙන් ඔබ ඔබේ දුරකථනය සඳහා සිදු වන යම් හානි හෝ එය භාවිත කිරීමේ ප්රතිඵලයක් ලෙස සිදු වන දත්ත හානි සඳහා ඔබ වගකිව යුතු බවට එකඟ වේ."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ඔබගේ ටැබ්ලට් පරිගණකය සහ පුද්ගලික දත්තවලට නොදන්නා යෙදුම් මඟින් තර්ජන එල්ල කිරීමේ හැකියාව වැඩිය. මෙම යෙදුම් ස්ථාපනය කිරීමෙන් සහ භාවිත කිරීමෙන් ඔබ ඔබේ ටැබ්ලට් පරිගණකය සඳහා සිදු වන යම් හානි හෝ එය භාවිත කිරීමේ ප්රතිඵලයක් ලෙස සිදු වන දත්ත හානි සඳහා ඔබ වගකිව යුතු බවට එකඟ වේ."</string>
diff --git a/packages/PackageInstaller/res/values-sk/strings.xml b/packages/PackageInstaller/res/values-sk/strings.xml
index c2b23d6..f9acae8 100644
--- a/packages/PackageInstaller/res/values-sk/strings.xml
+++ b/packages/PackageInstaller/res/values-sk/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Neznáma"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Váš tablet momentálne nemôže z bezpečnostných dôvodov inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v Nastaveniach."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Váš televízor momentálne nemôže z bezpečnostných dôvodov inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v Nastaveniach."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Z bezpečnostných dôvodov momentálne nemôžete v hodnikách inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v Nastaveniach."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Váš telefón momentálne nemôže z bezpečnostných dôvodov inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v nastaveniach."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Váš telefón a osobné údaje sú náchylnejšie na útok z neznámych aplikácií. Inštaláciou tejto aplikácie vyjadrujete súhlas s tým, že nesiete zodpovednosť za akékoľvek poškodenie telefónu alebo stratu údajov, ktoré by mohli nastať pri jej používaní."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Váš tablet a osobné dáta sú náchylnejšie na útok z neznámych aplikácií. Inštaláciou tejto aplikácie vyjadrujete súhlas s tým, že nesiete zodpovednosť za akékoľvek poškodenie tabletu alebo stratu dát, ktoré by mohli nastať pri jej používaní."</string>
diff --git a/packages/PackageInstaller/res/values-sl/strings.xml b/packages/PackageInstaller/res/values-sl/strings.xml
index 926598a..9bf794d 100644
--- a/packages/PackageInstaller/res/values-sl/strings.xml
+++ b/packages/PackageInstaller/res/values-sl/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Neznano"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Tablični računalnik zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Televizor zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Ura zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Telefon zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Neznane aplikacije lahko resno ogrozijo varnost telefona in osebnih podatkov. Z namestitvijo te aplikacije se strinjate, da ste sami odgovorni za morebitno škodo, nastalo v telefonu, ali izgubo podatkov, do katerih lahko pride zaradi uporabe te aplikacije."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Neznane aplikacije lahko resno ogrozijo varnost tabličnega računalnika in osebnih podatkov. Z namestitvijo te aplikacije se strinjate, da ste sami odgovorni za morebitno škodo, nastalo v tabličnem računalniku, ali izgubo podatkov, do katerih lahko pride zaradi uporabe te aplikacije."</string>
diff --git a/packages/PackageInstaller/res/values-sq/strings.xml b/packages/PackageInstaller/res/values-sq/strings.xml
index b8ada36..80c53f9 100644
--- a/packages/PackageInstaller/res/values-sq/strings.xml
+++ b/packages/PackageInstaller/res/values-sq/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"I panjohur"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Për sigurinë tënde, tableti yt nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Për sigurinë tënde, televizori yt nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Për sigurinë tënde, ora jote nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Për sigurinë tënde, telefoni yt nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefoni dhe të dhënat e tua personale janë më të cenueshme nga sulmet nga aplikacione të panjohura. Duke instaluar këtë aplikacion, ti pranon se je përgjegjës për çdo dëm ndaj telefonit tënd ose çdo humbje të të dhënave që mund të rezultojë nga përdorimi i tij."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableti dhe të dhënat e tua personale janë më të cenueshme nga sulmet nga aplikacione të panjohura. Duke instaluar këtë aplikacion, ti pranon se je përgjegjës për çdo dëm ndaj tabletit tënd ose çdo humbje të të dhënave që mund të rezultojë nga përdorimi i tij."</string>
diff --git a/packages/PackageInstaller/res/values-sr/strings.xml b/packages/PackageInstaller/res/values-sr/strings.xml
index d964d3f..eab43ce 100644
--- a/packages/PackageInstaller/res/values-sr/strings.xml
+++ b/packages/PackageInstaller/res/values-sr/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Непознато"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Из безбедносних разлога таблету тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у подешавањима."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Из безбедносних разлога телевизору тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у подешавањима."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Из безбедносних разлога сату тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у Подешавањима."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Из безбедносних разлога телефону тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у подешавањима."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефон и лични подаци су подложнији нападу непознатих апликација. Ако инсталирате ову апликацију, прихватате да сте одговорни за евентуална оштећења телефона или губитак података до којих може да дође због њеног коришћења."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таблет и лични подаци су подложнији нападу непознатих апликација. Ако инсталирате ову апликацију, прихватате да сте одговорни за евентуална оштећења таблета или губитак података до којих може да дође због њеног коришћења."</string>
diff --git a/packages/PackageInstaller/res/values-sv/strings.xml b/packages/PackageInstaller/res/values-sv/strings.xml
index 231e3e1..6378f4b 100644
--- a/packages/PackageInstaller/res/values-sv/strings.xml
+++ b/packages/PackageInstaller/res/values-sv/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Okänd"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Av säkerhetsskäl får okända appar från den här källan inte installeras på surfplattan. Du kan ändra det här i inställningarna."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Av säkerhetsskäl får okända appar från den här källan inte installeras på tv:n. Du kan ändra det här i inställningarna."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Av säkerhetsskäl får okända appar från den här källan inte installeras på klockan. Du kan ändra det här i inställningarna."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Av säkerhetsskäl får okända appar från den här källan inte installeras på telefonen. Du kan ändra det här i inställningarna."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Din mobil och personliga data är mer sårbara för attacker från okända appar. Genom att installera denna app bekräftar du att du är ansvarig för eventuella skador på mobilen och för dataförlust som kan uppstå vid användning av denna app."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Din surfplatta och personliga data är mer sårbara för attacker från okända appar. Genom att installera denna app bekräftar du att du är ansvarig för eventuella skador på surfplattan och för dataförlust som kan uppstå vid användning av denna app."</string>
diff --git a/packages/PackageInstaller/res/values-sw/strings.xml b/packages/PackageInstaller/res/values-sw/strings.xml
index 65b785f..454259f 100644
--- a/packages/PackageInstaller/res/values-sw/strings.xml
+++ b/packages/PackageInstaller/res/values-sw/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Haijulikani"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Kwa sababu ya usalama wako, kompyuta yako kibao kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Kwa sababu ya usalama wako, TV yako kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Kwa sababu ya usalama wako, saa yako kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Kwa sababu ya usalama wako, simu yako kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Data yako ya binafsi na ya simu yako inaweza kuathiriwa na programu ambazo hazijulikani. Kwa kusakinisha programu hii, unakubali kuwajibikia uharibifu wowote kwenye simu yako au kupotea kwa data kutokana na matumizi yake."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Data yako ya binafsi na ya kompyuta yako kibao inaweza kuathiriwa na programu ambazo hazijulikani. Kwa kusakinisha programu hii, unakubali kuwajibikia uharibifu wowote kwenye kompyuta yako kibao au kupotea kwa data kutokana na matumizi yake."</string>
diff --git a/packages/PackageInstaller/res/values-ta/strings.xml b/packages/PackageInstaller/res/values-ta/strings.xml
index 62e531a..b9ae9a4 100644
--- a/packages/PackageInstaller/res/values-ta/strings.xml
+++ b/packages/PackageInstaller/res/values-ta/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"அறியப்படாதது"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை டேப்லெட்டில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை டிவியில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை வாட்ச்சில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை மொபைலில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"அறியப்படாத ஆப்ஸால் உங்கள் மொபைலும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகலாம். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது மொபைலில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"அறியப்படாத ஆப்ஸால் உங்கள் டேப்லெட்டும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகலாம். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது டேப்லெட்டில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string>
diff --git a/packages/PackageInstaller/res/values-te/strings.xml b/packages/PackageInstaller/res/values-te/strings.xml
index c016bfc..186b28f 100644
--- a/packages/PackageInstaller/res/values-te/strings.xml
+++ b/packages/PackageInstaller/res/values-te/strings.xml
@@ -73,8 +73,8 @@
<string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"ఈ యాప్ కొందరు వినియోగదారులకు లేదా కొన్ని ప్రొఫైళ్లకు అవసరం, ఇతరులకు అన్ఇన్స్టాల్ చేయబడింది"</string>
<string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"మీ ప్రొఫైల్ కోసం ఈ యాప్ అవసరం, అందువల్ల దీన్ని అన్ఇన్స్టాల్ చేయడం కుదరదు."</string>
<string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"మీ పరికర నిర్వాహకులకు ఈ యాప్ అవసరం, అందువల్ల దీన్ని అన్ఇన్స్టాల్ చేయడం కుదరదు."</string>
- <string name="manage_device_administrators" msgid="3092696419363842816">"పరికర నిర్వాహక యాప్లను నిర్వహించు"</string>
- <string name="manage_users" msgid="1243995386982560813">"వినియోగదారులను నిర్వహించు"</string>
+ <string name="manage_device_administrators" msgid="3092696419363842816">"పరికర నిర్వాహక యాప్లను మేనేజ్ చేయండి"</string>
+ <string name="manage_users" msgid="1243995386982560813">"వినియోగదారులను మేనేజ్ చేయండి"</string>
<string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g>ని అన్ఇన్స్టాల్ చేయడం సాధ్యపడలేదు."</string>
<string name="Parse_error_dlg_text" msgid="1661404001063076789">"ప్యాకేజీని అన్వయించడంలో సమస్య ఏర్పడింది."</string>
<string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string>
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"తెలియదు"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్లను ఇన్స్టాల్ చేయడానికి మీ టాబ్లెట్ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్లలో మార్చవచ్చు."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్లను ఇన్స్టాల్ చేయడానికి మీ టీవీ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్లలో మార్చవచ్చు."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్లను ఇన్స్టాల్ చేయడానికి మీ వాచ్ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్లలో మార్చవచ్చు."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్లను ఇన్స్టాల్ చేయడానికి మీ ఫోన్ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్లలో మార్చవచ్చు."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"తెలియని యాప్లు మీ ఫోన్ పైన, వ్యక్తిగత డేటా పైన దాడి చేయడానికి ఎక్కువగా అవకాశం ఉంటుంది. ఈ యాప్ను ఇన్స్టాల్ చేయడం ద్వారా, దాని వినియోగంతో మీ ఫోన్కు ఏదైనా నష్టం జరిగితే లేదా మీ డేటాను కోల్పోతే అందుకు మీరే బాధ్యత వహిస్తారని అంగీకరిస్తున్నారు."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"మీ టాబ్లెట్ మరియు వ్యక్తిగత డేటాపై తెలియని యాప్లు దాడి చేయడానికి ఎక్కువ అవకాశం ఉంది. మీరు ఈ యాప్ను ఇన్స్టాల్ చేయడం ద్వారా, దీనిని ఉపయోగించడం వలన మీ టాబ్లెట్కు ఏదైనా హాని జరిగినా లేదా డేటా కోల్పోయినా బాధ్యత మీదేనని అంగీకరిస్తున్నారు."</string>
diff --git a/packages/PackageInstaller/res/values-th/strings.xml b/packages/PackageInstaller/res/values-th/strings.xml
index 1014152a..661c30f 100644
--- a/packages/PackageInstaller/res/values-th/strings.xml
+++ b/packages/PackageInstaller/res/values-th/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"ไม่ทราบ"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"เพื่อความปลอดภัย ปัจจุบันไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้ในแท็บเล็ต คุณเปลี่ยนแปลงได้ในการตั้งค่า"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"เพื่อความปลอดภัย ปัจจุบันไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้ในทีวี คุณเปลี่ยนแปลงได้ในการตั้งค่า"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ปัจจุบันนาฬิกาไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้เพื่อความปลอดภัยของคุณ คุณเปลี่ยนค่านี้ได้ในการตั้งค่า"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"เพื่อความปลอดภัย ปัจจุบันไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้ในโทรศัพท์ ซึ่งคุณเปลี่ยนเป็นอนุญาตได้ในการตั้งค่า"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"โทรศัพท์และข้อมูลส่วนตัวของคุณมีความเสี่ยงมากขึ้นที่จะถูกโจมตีจากแอปที่ไม่รู้จัก การติดตั้งแอปนี้แสดงว่าคุณยอมรับว่าจะรับผิดชอบต่อความเสียหายใดๆ ที่จะเกิดขึ้นกับโทรศัพท์หรือการสูญเสียข้อมูลที่อาจเกิดจากการใช้งานแอปดังกล่าว"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"แท็บเล็ตและข้อมูลส่วนตัวของคุณมีความเสี่ยงมากขึ้นที่จะถูกโจมตีจากแอปที่ไม่รู้จัก การติดตั้งแอปนี้แสดงว่าคุณยอมรับว่าจะรับผิดชอบต่อความเสียหายใดๆ ที่จะเกิดขึ้นกับแท็บเล็ตหรือการสูญเสียข้อมูลที่อาจเกิดจากการใช้งานแอปดังกล่าว"</string>
diff --git a/packages/PackageInstaller/res/values-tl/strings.xml b/packages/PackageInstaller/res/values-tl/strings.xml
index 5606eb5..d298fe3 100644
--- a/packages/PackageInstaller/res/values-tl/strings.xml
+++ b/packages/PackageInstaller/res/values-tl/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Hindi Kilala"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang tablet mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang TV mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang relo mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang telepono mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Mas nanganganib ang iyong telepono at personal na data sa mga pag-atake mula sa mga hindi kilalang app. Sa pamamagitan ng pag-install ng app na ito, sumasang-ayon kang ikaw ang responsable sa anumang pinsala sa iyong telepono o pagkawala ng data na maaaring magresulta mula sa paggamit nito."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Mas nanganganib ang iyong tablet at personal na data sa mga pag-atake mula sa mga hindi kilalang app. Sa pamamagitan ng pag-install ng app na ito, sumasang-ayon kang ikaw ang responsable sa anumang pinsala sa iyong tablet o pagkawala ng data na maaaring magresulta mula sa paggamit nito."</string>
diff --git a/packages/PackageInstaller/res/values-tr/strings.xml b/packages/PackageInstaller/res/values-tr/strings.xml
index 673511c..4abdfdf 100644
--- a/packages/PackageInstaller/res/values-tr/strings.xml
+++ b/packages/PackageInstaller/res/values-tr/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Bilinmiyor"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Güvenlik nedeniyle şu anda tabletinizin bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'da değiştirebilirsiniz."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Güvenlik nedeniyle şu anda TV\'nizin bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'da değiştirebilirsiniz."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Güvenlik nedeniyle şu anda tabletinizin bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'dan değiştirebilirsiniz."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Güvenlik nedeniyle şu anda telefonunuzun bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'da değiştirebilirsiniz."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonunuz ve kişisel verileriniz, bilinmeyen uygulamaların saldırılarına karşı daha savunmasızdır. Bu uygulamayı yükleyerek, uygulama kullanımından dolayı telefonunuzda oluşabilecek hasarın veya uğrayabileceğiniz veri kaybının sorumluluğunu kabul etmiş olursunuz."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletiniz ve kişisel verileriniz, bilinmeyen uygulamaların saldırılarına karşı daha savunmasızdır. Bu uygulamayı yükleyerek, uygulama kullanımından dolayı tabletinizde oluşabilecek hasarın veya uğrayabileceğiniz veri kaybının sorumluluğunu kabul etmiş olursunuz."</string>
diff --git a/packages/PackageInstaller/res/values-uk/strings.xml b/packages/PackageInstaller/res/values-uk/strings.xml
index 67b3e0b..c2d138a 100644
--- a/packages/PackageInstaller/res/values-uk/strings.xml
+++ b/packages/PackageInstaller/res/values-uk/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Невідомо"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"З міркувань безпеки на вашому планшеті зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"З міркувань безпеки на вашому телевізорі зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"З міркувань безпеки на вашому годиннику зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"З міркувань безпеки на вашому телефоні зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ваш телефон і особисті дані більш уразливі до атак невідомих додатків. Установлюючи цей додаток, ви берете на себе відповідальність за пошкодження телефона чи втрату даних унаслідок використання додатка."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ваш планшет і особисті дані більш уразливі до атак невідомих додатків. Установлюючи цей додаток, ви берете на себе відповідальність за пошкодження планшета чи втрату даних унаслідок використання додатка."</string>
diff --git a/packages/PackageInstaller/res/values-ur/strings.xml b/packages/PackageInstaller/res/values-ur/strings.xml
index 57fc568..1e256a8 100644
--- a/packages/PackageInstaller/res/values-ur/strings.xml
+++ b/packages/PackageInstaller/res/values-ur/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"نامعلوم"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"آپ کی سیکیورٹی کی خاطر فی الحال آپ کے ٹیبلیٹ کو اس ماخذ سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"آپ کی سیکیورٹی کی خاطر فی الحال آپ کے TV کو اس ماخذ سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"آپ کی سیکیورٹی کی خاطر، فی الحال آپ کی گھڑی کو اس ماخذ سے نامعلوم ایپس کو انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"آپ کی سیکیورٹی کی خاطر فی الحال آپ کے فون کو اس ماخذ سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"آپ کے فون اور ذاتی ڈیٹا کو نامعلوم ایپس کی جانب سے حملے کا زیادہ خطرہ ہے۔ اس ایپ کو انسٹال کر کے، آپ اس بات سے اتفاق کرتے ہیں کہ آپ اس سے اپنے فون کو ہونے والے کسی بھی نقصان یا ڈیٹا کے نقصان کے لئے خود ذمہ دار ہیں۔"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"آپ کے ٹیبلیٹ اور ذاتی ڈیٹا کو نامعلوم ایپس کی جانب سے حملے کا زیادہ خطرہ ہے۔ اس ایپ کو انسٹال کر کے، آپ اس بات سے اتفاق کرتے ہیں کہ آپ اس سے اپنے ٹیبلیٹ کو ہونے والے کسی بھی نقصان یا ڈیٹا کے نقصان کے لئے خود ذمہ دار ہیں۔"</string>
diff --git a/packages/PackageInstaller/res/values-uz/strings.xml b/packages/PackageInstaller/res/values-uz/strings.xml
index 275ac47..4f6349c 100644
--- a/packages/PackageInstaller/res/values-uz/strings.xml
+++ b/packages/PackageInstaller/res/values-uz/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Noaniq"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Xavfsizlik yuzasidan, planshetingizga hozirda bu manbadan notanish ilovalarni o‘rnatishga ruxsat berilmagan. Buni sozlamalardan oʻzgartirish mumkin."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Xavfsizlik yuzasidan, televizoringizga hozirda bu manbadan notanish ilovalarni o‘rnatishga ruxsat berilmagan. Buni sozlamalardan oʻzgartirish mumkin."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Xavfsizlik yuzasidan, soatingizga hozirda bu manbadan notanish ilovalarni oʻrnatishga ruxsat berilmagan. Buni sozlamalardan oʻzgartirish mumkin."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Xavfsizlik yuzasidan, telefoningizga hozir ushbu manbadan notanish ilovalarni o‘rnatishga ruxsat berilmagan. Buni Sozlamalarda oʻzgartirishingiz mumkin."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefoningiz va shaxsiy axborotlaringiz notanish ilovalar hujumiga zaif bo‘ladi. Bu ilovani o‘rnatish bilan telefoningizga yetkaziladigan shikast va axborotlaringizni o‘chirib yuborilishiga javobgarlikni o‘z zimmangizga olasiz."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Planshetingiz va shaxsiy axborotlaringiz notanish ilovalar hujumiga zaif bo‘ladi. Bu ilovani o‘rnatish bilan planshetingizga yetkaziladigan shikast va axborotlaringizni o‘chirib yuborilishiga javobgarlikni o‘z zimmangizga olasiz."</string>
diff --git a/packages/PackageInstaller/res/values-vi/strings.xml b/packages/PackageInstaller/res/values-vi/strings.xml
index eacbe1d..62bca19 100644
--- a/packages/PackageInstaller/res/values-vi/strings.xml
+++ b/packages/PackageInstaller/res/values-vi/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Không xác định"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Để bảo mật, máy tính bảng của bạn hiện không được phép cài đặt các ứng dụng không xác định từ nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Để bảo mật, TV của bạn hiện không được phép cài đặt các ứng dụng không xác định từ nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Để bảo mật cho bạn, đồng hồ của bạn hiện không được phép cài đặt những ứng dụng không xác định qua nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Để bảo mật, điện thoại của bạn hiện không được phép cài đặt các ứng dụng không xác định từ nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Điện thoại và dữ liệu cá nhân của bạn dễ bị các ứng dụng không xác định tấn công hơn. Bằng cách cài đặt ứng dụng này, bạn đồng ý tự chịu trách nhiệm cho mọi hỏng hóc đối với điện thoại của mình hoặc mất mát dữ liệu có thể phát sinh do sử dụng ứng dụng này."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Máy tính bảng và dữ liệu cá nhân của bạn dễ bị các ứng dụng không xác định tấn công hơn. Bằng cách cài đặt ứng dụng này, bạn đồng ý tự chịu trách nhiệm cho mọi hỏng hóc đối với máy tính bảng của mình hoặc mất mát dữ liệu có thể phát sinh do sử dụng ứng dụng này."</string>
diff --git a/packages/PackageInstaller/res/values-zh-rCN/strings.xml b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
index cbebb21..9d9824e 100644
--- a/packages/PackageInstaller/res/values-zh-rCN/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"未知"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"出于安全考虑,目前已禁止您的平板电脑安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"出于安全考虑,目前已禁止您的电视安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"出于安全考虑,目前已禁止您的手表安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"出于安全考虑,您的手机目前不允许安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"来历不明的应用很可能会损害您的手机和个人数据。安装该应用即表示,您同意对于因使用该应用可能导致的任何手机损坏或数据丢失情况,您负有全部责任。"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"来历不明的应用很可能会损害您的平板电脑和个人数据。安装该应用即表示,您同意对于因使用该应用可能导致的任何平板电脑损坏或数据丢失情况,您负有全部责任。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rHK/strings.xml b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
index dce8cfe..9535153 100644
--- a/packages/PackageInstaller/res/values-zh-rHK/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"不明"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"為安全起見,您的平板電腦目前不得安裝此來源的不明應用程式。您可以在「設定」中變更這項設定。"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"為安全起見,您的電視目前不得安裝此來源的不明應用程式。您可以在「設定」中變更這項設定。"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"為了安全起見,你的智慧手錶目前禁止安裝這個來源的不明應用程式。如要變更,請前往「設定」。"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"為安全起見,您的手機目前不得安裝此來源的不明應用程式。您可以在「設定」中變更這項設定。"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"來源不明的應用程式可能會侵害您的手機和個人資料。安裝此應用程式,即表示您同意承擔因使用這個應用程式而導致手機損壞或資料遺失的責任。"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"來源不明的應用程式可能會侵害您的平板電腦和個人資料。安裝此應用程式,即表示您同意承擔因使用這個應用程式而導致平板電腦損壞或資料遺失的責任。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rTW/strings.xml b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
index 67c684b..f53791b 100644
--- a/packages/PackageInstaller/res/values-zh-rTW/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"不明"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"為了安全起見,你的平板電腦目前禁止安裝這個來源提供的不明應用程式。如要進行變更,請前往「設定」。"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"為了安全起見,你的電視目前禁止安裝這個來源提供的不明應用程式。如要進行變更,請前往「設定」。"</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"為了安全起見,你的智慧手錶目前禁止安裝這個來源的不明應用程式。如要變更,請前往「設定」。"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"為了安全起見,你的手機目前無法安裝此來源提供的不明應用程式。如要進行變更,請前往「設定」。"</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"來歷不明的應用程式可能會損害你的手機和個人資料。如因安裝及使用這個應用程式,導致你的手機受損或資料遺失,請自行負責。"</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"來歷不明的應用程式可能會損害你的平板電腦和個人資料。如因安裝及使用這個應用程式,導致你的平板電腦受損或資料遺失,請自行負責。"</string>
diff --git a/packages/PackageInstaller/res/values-zu/strings.xml b/packages/PackageInstaller/res/values-zu/strings.xml
index ef88552..5aa13eb 100644
--- a/packages/PackageInstaller/res/values-zu/strings.xml
+++ b/packages/PackageInstaller/res/values-zu/strings.xml
@@ -83,6 +83,7 @@
<string name="app_name_unknown" msgid="6881210203354323926">"Akwaziwa"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Mayelana nokuphepha kwakho, ithebulethi yakho okwamanje ayivumelekanga ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Mayelana nokuphepha kwakho, i-TV yakho okwamanje ayivumelekanga ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
+ <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Ngenxa yokuvikeleka kwakho, ithebulethi yakho okwamanje ayivumelekanga ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Mayelana nokuvikeleka kwakho, ifoni yakho okwamanje ayivunyelwe ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Idatha yakho yefoni neyohlelo lwakho lokusebenza isengcupheni kakhulu ekuhlaselweni izinhlelo zokusebenza ezingaziwa. Ngokufaka lolu hlelo lokusebenza, uyavuma ukuthi unesibopho sanoma ikuphi ukonakala kufoni yakho noma ukulahlekelwa kwedatha okungabangelwa ukusetshenziswa kwayo."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ithebulethi yakho nedatha yomuntu siqu zisengcupheni kakhulu ekuhlaselweni izinhlelo zokusebenza ezingaziwa. Ngokufaka lolu hlelo lokusebenza, uyavuma ukuthi unesibopho sanoma ikuphi ukonakala kuthebulethi yakho noma ukulahleka kwedatha okungabangelwa ukusetshenziswa kwayo."</string>
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 688d116..6a3b239 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -189,6 +189,9 @@
<!-- Text to show in warning dialog on the tv when the app source is not trusted [CHAR LIMIT=NONE] -->
<string name="untrusted_external_source_warning" product="tv">For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
+ <!-- Text to show in warning dialog on the wear when the app source is not trusted [CHAR LIMIT=NONE] -->
+ <string name="untrusted_external_source_warning" product="watch">For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
+
<!-- Text to show in warning dialog on the phone when the app source is not trusted [CHAR LIMIT=NONE] -->
<string name="untrusted_external_source_warning" product="default">For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index bfab9be..e4bdab8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -28,6 +28,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
@@ -35,6 +36,8 @@
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import java.util.Arrays;
@@ -96,6 +99,23 @@
mAbortInstall = true;
}
}
+
+ final String installerPackageNameFromIntent = getIntent().getStringExtra(
+ Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ if (installerPackageNameFromIntent != null) {
+ final String callingPkgName = getLaunchedFromPackage();
+ if (installerPackageNameFromIntent.length() >= SessionParams.MAX_PACKAGE_NAME_LENGTH
+ || (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
+ && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+ callingPkgName) != PackageManager.PERMISSION_GRANTED)) {
+ Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent
+ + " is invalid. Remove it.");
+ EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
+ "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
+ getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ }
+ }
+
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index de76632..fa93670 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -124,9 +124,8 @@
private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
- private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
- private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 8;
- private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;
+ private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 7;
+ private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 8;
// If unknown sources are temporary allowed
private boolean mAllowUnknownSources;
@@ -189,8 +188,6 @@
case DLG_INSTALL_ERROR:
return InstallErrorDialog.newInstance(
mPm.getApplicationLabel(mPkgInfo.applicationInfo));
- case DLG_NOT_SUPPORTED_ON_WEAR:
- return NotSupportedOnWearDialog.newInstance();
case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
return SimpleErrorDialog.newInstance(
R.string.install_apps_user_restriction_dlg_text);
@@ -379,12 +376,8 @@
return;
}
- if (DeviceUtils.isWear(this)) {
- showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
- return;
- }
-
final boolean wasSetUp = processAppSnippet(packageSource);
+
if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
if (!wasSetUp) {
@@ -779,21 +772,6 @@
}
/**
- * An error dialog shown when the app is not supported on wear
- */
- public static class NotSupportedOnWearDialog extends SimpleErrorDialog {
- static SimpleErrorDialog newInstance() {
- return SimpleErrorDialog.newInstance(R.string.wear_not_allowed_dlg_text);
- }
-
- @Override
- public void onCancel(DialogInterface dialog) {
- getActivity().setResult(RESULT_OK);
- getActivity().finish();
- }
- }
-
- /**
* An error dialog shown when the device is out of space
*/
public static class OutOfSpaceDialog extends AppErrorDialog {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
index 93e6271..3029d10 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
@@ -55,18 +55,15 @@
private static final String PRIVET_SERVICE = "_privet._tcp";
/** The required mDNS service types */
- private static final Set<String> PRINTER_SERVICE_TYPE = new HashSet<String>() {{
- // Not checking _printer_._sub
- add(PRIVET_SERVICE);
- }};
+ private static final Set<String> PRINTER_SERVICE_TYPE = Set.of(
+ PRIVET_SERVICE); // Not checking _printer_._sub
/** All possible connection states */
- private static final Set<String> POSSIBLE_CONNECTION_STATES = new HashSet<String>() {{
- add("online");
- add("offline");
- add("connecting");
- add("not-configured");
- }};
+ private static final Set<String> POSSIBLE_CONNECTION_STATES = Set.of(
+ "online",
+ "offline",
+ "connecting",
+ "not-configured");
private static final byte SUPPORTED_TXTVERS = '1';
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index 34e7e3d..0c5de27 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -37,9 +37,7 @@
public class MDNSFilterPlugin implements PrintServicePlugin {
/** The mDNS service types supported */
- private static final Set<String> PRINTER_SERVICE_TYPES = new HashSet<String>() {{
- add("_ipp._tcp");
- }};
+ private static final Set<String> PRINTER_SERVICE_TYPES = Set.of("_ipp._tcp");
/**
* The printer filter for {@link MDNSFilteredDiscovery} passing only mDNS results
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
index d03bb1d..b9983c3 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
@@ -23,7 +23,6 @@
import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.util.MDNSUtils;
-import java.util.HashSet;
import java.util.Set;
/**
@@ -32,10 +31,7 @@
class PrinterFilterMopria implements MDNSFilteredDiscovery.PrinterFilter {
private static final String TAG = "PrinterFilterMopria";
- static final Set<String> MOPRIA_MDNS_SERVICES = new HashSet<String>() {{
- add("_ipp._tcp");
- add("_ipps._tcp");
- }};
+ static final Set<String> MOPRIA_MDNS_SERVICES = Set.of("_ipp._tcp", "_ipps._tcp");
private static final String PDL__PDF = "application/pdf";
private static final String PDL__PCLM = "application/PCLm";
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
index b9b9098..680dd84 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
@@ -25,7 +25,6 @@
import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.util.MDNSUtils;
-import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -36,9 +35,7 @@
class PrinterFilterSamsung implements MDNSFilteredDiscovery.PrinterFilter {
private static final String TAG = "PrinterFilterSamsung";
- static final Set<String> SAMSUNG_MDNS_SERVICES = new HashSet<String>() {{
- add("_pdl-datastream._tcp");
- }};
+ static final Set<String> SAMSUNG_MDNS_SERVICES = Set.of("_pdl-datastream._tcp");
private static final String[] NOT_SUPPORTED_MODELS = new String[]{
"SCX-5x15",
@@ -57,9 +54,7 @@
private static final String ATTR_PRODUCT = "product";
private static final String ATTR_TY = "ty";
- private static Set<String> SAMUNG_VENDOR_SET = new HashSet<String>() {{
- add("samsung");
- }};
+ private static final Set<String> SAMUNG_VENDOR_SET = Set.of("samsung");
@Override
public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
index ae1bdce..cbd5833 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
@@ -29,10 +29,11 @@
import java.util.Set;
public class SamsungRecommendationPlugin implements PrintServicePlugin {
- private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>() {{
- addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
- addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
- }};
+ private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>();
+ static {
+ ALL_MDNS_SERVICES.addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
+ ALL_MDNS_SERVICES.addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
+ }
private final @NonNull Context mContext;
private final @NonNull MDNSFilteredDiscovery mMDNSFilteredDiscovery;
diff --git a/packages/PrintSpooler/res/values-en-rCA/strings.xml b/packages/PrintSpooler/res/values-en-rCA/strings.xml
index 7fbfeb3..606cc3e 100644
--- a/packages/PrintSpooler/res/values-en-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rCA/strings.xml
@@ -23,21 +23,21 @@
<string name="label_copies_summary" msgid="3861966063536529540">"Copies:"</string>
<string name="label_paper_size" msgid="908654383827777759">"Paper size"</string>
<string name="label_paper_size_summary" msgid="5668204981332138168">"Paper size:"</string>
- <string name="label_color" msgid="1108690305218188969">"Colour"</string>
+ <string name="label_color" msgid="1108690305218188969">"Color"</string>
<string name="label_duplex" msgid="5370037254347072243">"Two-sided"</string>
<string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
<string name="label_pages" msgid="7768589729282182230">"Pages"</string>
<string name="destination_default_text" msgid="5422708056807065710">"Select a printer"</string>
<string name="template_all_pages" msgid="3322235982020148762">"All <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
<string name="template_page_range" msgid="428638530038286328">"Range of <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
- <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
+ <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1—5,8,11—13"</string>
<string name="print_preview" msgid="8010217796057763343">"Print preview"</string>
<string name="install_for_print_preview" msgid="6366303997385509332">"Install PDF viewer for preview"</string>
<string name="printing_app_crashed" msgid="854477616686566398">"Printing app crashed"</string>
<string name="generating_print_job" msgid="3119608742651698916">"Generating print job"</string>
<string name="save_as_pdf" msgid="5718454119847596853">"Save as PDF"</string>
<string name="all_printers" msgid="5018829726861876202">"All printers…"</string>
- <string name="print_dialog" msgid="32628687461331979">"Print dialogue"</string>
+ <string name="print_dialog" msgid="32628687461331979">"Print dialog"</string>
<string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
<string name="page_description_template" msgid="6831239682256197161">"Page <xliff:g id="CURRENT_PAGE">%1$d</xliff:g> of <xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
<string name="summary_template" msgid="8899734908625669193">"Summary, copies <xliff:g id="COPIES">%1$s</xliff:g>, paper size <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
@@ -91,7 +91,7 @@
<string name="print_service_security_warning_summary" msgid="1427434625361692006">"Your document may pass through one or more servers on its way to the printer."</string>
<string-array name="color_mode_labels">
<item msgid="7602948745415174937">"Black & White"</item>
- <item msgid="2762241247228983754">"Colour"</item>
+ <item msgid="2762241247228983754">"Color"</item>
</string-array>
<string-array name="duplex_mode_labels">
<item msgid="3882302912790928315">"None"</item>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 00b3736..b0aa8f1 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -402,7 +402,7 @@
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
- if ((isOptionsClosed() || isOptionsClosed()) && dy <= 0) {
+ if (isOptionsClosed() && dy <= 0) {
return;
}
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 8eafbdf..a53782a 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,10 +3,11 @@
edgarwang@google.com
emilychuang@google.com
evanlaird@google.com
+hanxu@google.com
juliacr@google.com
leifhendrik@google.com
-tmfang@google.com
virgild@google.com
+ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
per-file *.xml=*
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 4fb77d7..b8fd579 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,9 +16,10 @@
buildscript {
ext {
- spa_min_sdk = 21
- spa_target_sdk = 33
- jetpack_compose_version = '1.3.0'
+ BUILD_TOOLS_VERSION = "30.0.3"
+ MIN_SDK = 21
+ TARGET_SDK = 33
+ jetpack_compose_version = '1.4.0-alpha01'
jetpack_compose_compiler_version = '1.3.2'
}
}
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index f1a24af..1e52aaf 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -35,12 +35,26 @@
</activity>
<provider
- android:name=".GalleryEntryProvider"
- android:authorities="com.android.spa.gallery.provider"
+ android:name="com.android.settingslib.spa.framework.SpaSearchProvider"
+ android:authorities="com.android.spa.gallery.search.provider"
android:enabled="true"
android:exported="false">
</provider>
+ <provider android:name="com.android.settingslib.spa.framework.SpaSliceProvider"
+ android:authorities="com.android.spa.gallery.slice.provider"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.app.slice.category.SLICE" />
+ </intent-filter>
+ </provider>
+
+ <receiver
+ android:name="com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver"
+ android:exported="false">
+ </receiver>
+
<activity
android:name="com.android.settingslib.spa.framework.debug.BlankActivity"
android:exported="true">
@@ -51,7 +65,7 @@
</activity>
<provider
android:name="com.android.settingslib.spa.framework.debug.DebugProvider"
- android:authorities="com.android.spa.gallery.debug"
+ android:authorities="com.android.spa.gallery.debug.provider"
android:enabled="true"
android:exported="false">
</provider>
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index e04a9be..7868aff 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -21,12 +21,13 @@
android {
namespace 'com.android.settingslib.spa.gallery'
- compileSdk spa_target_sdk
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig {
applicationId "com.android.settingslib.spa.gallery"
- minSdk spa_min_sdk
- targetSdk spa_target_sdk
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
versionCode 1
versionName "1.0"
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 742e271..941e770 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.gallery
import android.content.Context
+import com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver
import com.android.settingslib.spa.framework.common.LocalLogger
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironment
@@ -79,8 +80,8 @@
}
override val browseActivityClass = GalleryMainActivity::class.java
-
- override val entryProviderAuthorities = "com.android.spa.gallery.provider"
-
+ override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
+ override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
+ override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
override val logger = LocalLogger()
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 26e59ff..ff89f2b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -27,6 +27,7 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -37,6 +38,7 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -46,10 +48,15 @@
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+import com.android.settingslib.spa.slice.provider.createDemoActionSlice
+import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
+import com.android.settingslib.spa.slice.provider.createDemoSlice
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.ui.SettingsIcon
+import kotlinx.coroutines.delay
private const val TAG = "PreferencePage"
@@ -134,6 +141,26 @@
override val enabled = model.asyncEnable
}
)
+ }
+ .setSliceDataFn { sliceUri, _ ->
+ val createSliceImpl = { s: String ->
+ createDemoBrowseSlice(
+ sliceUri = sliceUri,
+ title = ASYNC_PREFERENCE_TITLE,
+ summary = s,
+ )
+ }
+ return@setSliceDataFn object : EntrySliceData() {
+ init {
+ postValue(createSliceImpl("(loading)"))
+ }
+
+ override suspend fun asyncRunner() {
+ spaLogger.message(TAG, "Async entry loading")
+ delay(2000L)
+ postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
+ }
+ }
}.build()
)
entryList.add(
@@ -152,6 +179,27 @@
}
}
)
+ }.setSliceDataFn { sliceUri, args ->
+ val createSliceImpl = { v: Int ->
+ createDemoActionSlice(
+ sliceUri = sliceUri,
+ title = MANUAL_UPDATE_PREFERENCE_TITLE,
+ summary = "manual update value $v",
+ )
+ }
+
+ return@setSliceDataFn object : EntrySliceData() {
+ private var tick = args?.getString("init")?.toInt() ?: 0
+
+ init {
+ postValue(createSliceImpl(tick))
+ }
+
+ override suspend fun asyncAction() {
+ tick++
+ postValue(createSliceImpl(tick))
+ }
+ }
}.build()
)
entryList.add(
@@ -170,7 +218,33 @@
}
)
}
- .build()
+ .setSliceDataFn { sliceUri, args ->
+ val createSliceImpl = { v: Int ->
+ createDemoBrowseSlice(
+ sliceUri = sliceUri,
+ title = AUTO_UPDATE_PREFERENCE_TITLE,
+ summary = "auto update value $v",
+ )
+ }
+
+ return@setSliceDataFn object : EntrySliceData() {
+ private var tick = args?.getString("init")?.toInt() ?: 0
+
+ init {
+ postValue(createSliceImpl(tick))
+ }
+
+ override suspend fun asyncRunner() {
+ spaLogger.message(TAG, "autoUpdater.active")
+ while (true) {
+ delay(1000L)
+ tick++
+ spaLogger.message(TAG, "autoUpdater.value $tick")
+ postValue(createSliceImpl(tick))
+ }
+ }
+ }
+ }.build()
)
return entryList
@@ -201,6 +275,22 @@
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
)
}
+ .setSliceDataFn { sliceUri, _ ->
+ val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+ ?: return@setSliceDataFn null
+ return@setSliceDataFn object : EntrySliceData() {
+ init {
+ postValue(
+ createDemoSlice(
+ sliceUri = sliceUri,
+ title = PAGE_TITLE,
+ summary = "Injected Entry",
+ intent = intent,
+ )
+ )
+ }
+ }
+ }
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index d874417..fc6f10f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -44,7 +44,7 @@
const val DISABLE_PREFERENCE_TITLE = "Disabled"
const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
const val ASYNC_PREFERENCE_TITLE = "Async Preference"
- private const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
+ const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index cef79c1..b627a70 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -32,4 +32,5 @@
rootProject.name = "SpaLib"
include ':spa'
include ':gallery'
+include ':testutils'
include ':tests'
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 037b45e..eb7aaa7 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -24,6 +24,9 @@
srcs: ["src/**/*.kt"],
static_libs: [
+ "androidx.slice_slice-builders",
+ "androidx.slice_slice-core",
+ "androidx.slice_slice-view",
"androidx.compose.material3_material3",
"androidx.compose.material_material-icons-extended",
"androidx.compose.runtime_runtime",
@@ -37,7 +40,6 @@
],
kotlincflags: [
"-Xjvm-default=all",
- "-opt-in=kotlin.RequiresOptIn",
],
min_sdk_version: "31",
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 7a20c747..8543596 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -21,11 +21,12 @@
android {
namespace 'com.android.settingslib.spa'
- compileSdk spa_target_sdk
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig {
- minSdk spa_min_sdk
- targetSdk spa_target_sdk
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
}
sourceSets {
@@ -43,7 +44,7 @@
}
kotlinOptions {
jvmTarget = '1.8'
- freeCompilerArgs = ["-Xjvm-default=all", "-opt-in=kotlin.RequiresOptIn"]
+ freeCompilerArgs = ["-Xjvm-default=all"]
}
buildFeatures {
compose true
@@ -55,6 +56,9 @@
dependencies {
api "androidx.appcompat:appcompat:1.7.0-alpha01"
+ api "androidx.slice:slice-builders:1.1.0-alpha02"
+ api "androidx.slice:slice-core:1.1.0-alpha02"
+ api "androidx.slice:slice-view:1.1.0-alpha02"
api "androidx.compose.material3:material3:1.1.0-alpha01"
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
similarity index 96%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
index 38f41bc..3689e4e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
@@ -33,7 +33,7 @@
import com.android.settingslib.spa.framework.common.addUri
import com.android.settingslib.spa.framework.common.getColumns
-private const val TAG = "EntryProvider"
+private const val TAG = "SpaSearchProvider"
/**
* The content provider to return entry related data, which can be used for search and hierarchy.
@@ -47,7 +47,7 @@
* $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
* $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
*/
-open class EntryProvider : ContentProvider() {
+class SpaSearchProvider : ContentProvider() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
@@ -119,7 +119,7 @@
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
- if (!entry.isAllowSearch || entry.mutableStatus) continue
+ if (!entry.isAllowSearch || entry.hasMutableStatus) continue
fetchStatusData(entry, cursor)
}
return cursor
@@ -129,7 +129,7 @@
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
- if (!entry.isAllowSearch || !entry.mutableStatus) continue
+ if (!entry.isAllowSearch || !entry.hasMutableStatus) continue
fetchStatusData(entry, cursor)
}
return cursor
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
new file mode 100644
index 0000000..58131e6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.settingslib.spa.framework
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+class SpaSliceBroadcastReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+ val sliceUri = intent?.data ?: return
+ val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
+ sliceData.doAction()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
new file mode 100644
index 0000000..faa04fd
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.settingslib.spa.framework
+
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.Observer
+import androidx.slice.Slice
+import androidx.slice.SliceProvider
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+private const val TAG = "SpaSliceProvider"
+
+class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
+ private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
+ if (!SpaEnvironmentFactory.isReady()) return null
+ val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+ return sliceRepository.getOrBuildSliceData(sliceUri)
+ }
+
+ override fun onBindSlice(sliceUri: Uri): Slice? {
+ if (context == null) return null
+ Log.d(TAG, "onBindSlice: $sliceUri")
+ return getOrPutSliceData(sliceUri)?.value
+ }
+
+ override fun onSlicePinned(sliceUri: Uri) {
+ Log.d(TAG, "onSlicePinned: $sliceUri")
+ super.onSlicePinned(sliceUri)
+ val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+ runBlocking {
+ withContext(Dispatchers.Main) {
+ sliceLiveData.observeForever(this@SpaSliceProvider)
+ }
+ }
+ }
+
+ override fun onSliceUnpinned(sliceUri: Uri) {
+ Log.d(TAG, "onSliceUnpinned: $sliceUri")
+ super.onSliceUnpinned(sliceUri)
+ val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+ runBlocking {
+ withContext(Dispatchers.Main) {
+ sliceLiveData.removeObserver(this@SpaSliceProvider)
+ }
+ }
+ }
+
+ override fun onChanged(slice: Slice?) {
+ val uri = slice?.uri ?: return
+ Log.d(TAG, "onChanged: $uri")
+ context?.contentResolver?.notifyChange(uri, null)
+ }
+
+ override fun onCreateSliceProvider(): Boolean {
+ Log.d(TAG, "onCreateSliceProvider")
+ return true
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt
new file mode 100644
index 0000000..fc551a8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.settingslib.spa.framework.common
+
+import androidx.lifecycle.LiveData
+import androidx.slice.Slice
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+open class EntrySliceData : LiveData<Slice?>() {
+ private val asyncRunnerScope = CoroutineScope(Dispatchers.IO)
+ private var asyncRunnerJob: Job? = null
+ private var asyncActionJob: Job? = null
+ private var isActive = false
+
+ open suspend fun asyncRunner() {}
+
+ open suspend fun asyncAction() {}
+
+ override fun onActive() {
+ asyncRunnerJob?.cancel()
+ asyncRunnerJob = asyncRunnerScope.launch { asyncRunner() }
+ isActive = true
+ }
+
+ override fun onInactive() {
+ asyncRunnerJob?.cancel()
+ asyncRunnerJob = null
+ asyncActionJob?.cancel()
+ asyncActionJob = null
+ isActive = false
+ }
+
+ fun isActive(): Boolean {
+ return isActive
+ }
+
+ fun doAction() {
+ asyncActionJob?.cancel()
+ asyncActionJob = asyncRunnerScope.launch { asyncAction() }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 224fe1d..9ee7f9e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.net.Uri
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -39,6 +40,11 @@
val LocalEntryDataProvider =
compositionLocalOf<EntryData> { object : EntryData {} }
+typealias UiLayerRenderer = @Composable (arguments: Bundle?) -> Unit
+typealias StatusDataGetter = (arguments: Bundle?) -> EntryStatusData?
+typealias SearchDataGetter = (arguments: Bundle?) -> EntrySearchData?
+typealias SliceDataGetter = (sliceUri: Uri, arguments: Bundle?) -> EntrySliceData?
+
/**
* Defines data of a Settings entry.
*/
@@ -71,7 +77,10 @@
// Indicate whether the status of entry is mutable.
// If so, for instance, we'll reindex its status for search.
- val mutableStatus: Boolean = false,
+ val hasMutableStatus: Boolean = false,
+
+ // Indicate whether the entry has SliceProvider support.
+ val hasSliceSupport: Boolean = false,
/**
* ========================================
@@ -83,13 +92,19 @@
* API to get the status data of the entry, such as isDisabled / isSwitchOff.
* Returns null if this entry do NOT have any status.
*/
- private val statusDataImpl: (arguments: Bundle?) -> EntryStatusData? = { null },
+ private val statusDataImpl: StatusDataGetter = { null },
/**
* API to get Search indexing data for this entry, such as title / keyword.
* Returns null if this entry do NOT support search.
*/
- private val searchDataImpl: (arguments: Bundle?) -> EntrySearchData? = { null },
+ private val searchDataImpl: SearchDataGetter = { null },
+
+ /**
+ * API to get Slice data of this entry. The Slice data is implemented as a LiveData,
+ * and is associated with the Slice's lifecycle (pin / unpin) by the framework.
+ */
+ private val sliceDataImpl: SliceDataGetter = { _: Uri, _: Bundle? -> null },
/**
* API to Render UI of this entry directly. For now, we use it in the internal injection, to
@@ -97,7 +112,7 @@
* injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and
* use each entries' UI rendering function in the page instead.
*/
- private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
+ private val uiLayoutImpl: UiLayerRenderer = {},
) {
fun containerPage(): SettingsPage {
// The Container page of the entry, which is the from-page or
@@ -121,6 +136,10 @@
return searchDataImpl(fullArgument(runtimeArguments))
}
+ fun getSliceData(sliceUri: Uri, runtimeArguments: Bundle? = null): EntrySliceData? {
+ return sliceDataImpl(sliceUri, fullArgument(runtimeArguments))
+ }
+
@Composable
fun UiLayout(runtimeArguments: Bundle? = null) {
CompositionLocalProvider(provideLocalEntryData()) {
@@ -152,12 +171,14 @@
// Attributes
private var isAllowSearch: Boolean = false
private var isSearchDataDynamic: Boolean = false
- private var mutableStatus: Boolean = false
+ private var hasMutableStatus: Boolean = false
+ private var hasSliceSupport: Boolean = false
// Functions
- private var statusDataFn: (arguments: Bundle?) -> EntryStatusData? = { null }
- private var searchDataFn: (arguments: Bundle?) -> EntrySearchData? = { null }
- private var uiLayoutFn: (@Composable (arguments: Bundle?) -> Unit) = { }
+ private var uiLayoutFn: UiLayerRenderer = { }
+ private var statusDataFn: StatusDataGetter = { null }
+ private var searchDataFn: SearchDataGetter = { null }
+ private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
fun build(): SettingsEntry {
return SettingsEntry(
@@ -173,11 +194,13 @@
// attributes
isAllowSearch = isAllowSearch,
isSearchDataDynamic = isSearchDataDynamic,
- mutableStatus = mutableStatus,
+ hasMutableStatus = hasMutableStatus,
+ hasSliceSupport = hasSliceSupport,
// functions
statusDataImpl = statusDataFn,
searchDataImpl = searchDataFn,
+ sliceDataImpl = sliceDataFn,
uiLayoutImpl = uiLayoutFn,
)
}
@@ -207,7 +230,7 @@
}
fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder {
- this.mutableStatus = hasMutableStatus
+ this.hasMutableStatus = hasMutableStatus
return this
}
@@ -221,17 +244,23 @@
return this
}
- fun setStatusDataFn(fn: (arguments: Bundle?) -> EntryStatusData?): SettingsEntryBuilder {
+ fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder {
this.statusDataFn = fn
return this
}
- fun setSearchDataFn(fn: (arguments: Bundle?) -> EntrySearchData?): SettingsEntryBuilder {
+ fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
this.searchDataFn = fn
return this
}
- fun setUiLayoutFn(fn: @Composable (arguments: Bundle?) -> Unit): SettingsEntryBuilder {
+ fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder {
+ this.sliceDataFn = fn
+ this.hasSliceSupport = true
+ return this
+ }
+
+ fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder {
this.uiLayoutFn = fn
return this
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index bb287d1..a372bbd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -113,6 +113,12 @@
)
}
+ fun createBrowseIntent(entryId: String? = null): Intent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
+ return createBrowseIntent(context, browseActivityClass, entryId)
+ }
+
fun createBrowseIntent(
context: Context?,
browseActivityClass: Class<out Activity>?,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 151b50cd..60599d4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -30,14 +30,14 @@
val name: String
/** The display name of this page provider, for better readability. */
- val displayName: String?
- get() = null
+ val displayName: String
+ get() = name
/** The page parameters, default is no parameters. */
val parameter: List<NamedNavArgument>
get() = emptyList()
- fun getTitle(arguments: Bundle?): String = displayName ?: name
+ fun getTitle(arguments: Bundle?): String = displayName
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index b831043..945add4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,10 +17,12 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
+import android.content.BroadcastReceiver
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.slice.SettingsSliceDataRepository
private const val TAG = "SpaEnvironment"
@@ -46,6 +48,10 @@
Log.d(TAG, "resetForPreview")
}
+ fun isReady(): Boolean {
+ return spaEnvironment != null
+ }
+
val instance: SpaEnvironment
get() {
if (spaEnvironment == null)
@@ -59,13 +65,15 @@
val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
+ val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
+
// In Robolectric test, applicationContext is not available. Use context as fallback.
val appContext: Context = context.applicationContext ?: context
open val browseActivityClass: Class<out Activity>? = null
-
- open val entryProviderAuthorities: String? = null
-
+ open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
+ open val searchProviderAuthorities: String? = null
+ open val sliceProviderAuthorities: String? = null
open val logger: SpaLogger = object : SpaLogger {}
// TODO: add other environment setup here.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 26491d5..760064a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.debug
+import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -38,6 +39,8 @@
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.slice.appendSliceParams
+import com.android.settingslib.spa.slice.presenter.SliceDemo
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -47,6 +50,7 @@
private const val ROUTE_ROOT = "root"
private const val ROUTE_All_PAGES = "pages"
private const val ROUTE_All_ENTRIES = "entries"
+private const val ROUTE_All_SLICES = "slices"
private const val ROUTE_PAGE = "page"
private const val ROUTE_ENTRY = "entry"
private const val PARAM_NAME_PAGE_ID = "pid"
@@ -81,6 +85,7 @@
composable(route = ROUTE_ROOT) { RootPage() }
composable(route = ROUTE_All_PAGES) { AllPages() }
composable(route = ROUTE_All_ENTRIES) { AllEntries() }
+ composable(route = ROUTE_All_SLICES) { AllSlices() }
composable(
route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
arguments = listOf(
@@ -102,6 +107,8 @@
val entryRepository by spaEnvironment.entryRepository
val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
val allEntry = remember { entryRepository.getAllEntries() }
+ val allSliceEntry =
+ remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
HomeScaffold(title = "Settings Debug") {
Preference(object : PreferenceModel {
override val title = "List All Pages (${allPageWithEntry.size})"
@@ -111,6 +118,10 @@
override val title = "List All Entries (${allEntry.size})"
override val onClick = navigator(route = ROUTE_All_ENTRIES)
})
+ Preference(object : PreferenceModel {
+ override val title = "List All Slices (${allSliceEntry.size})"
+ override val onClick = navigator(route = ROUTE_All_SLICES)
+ })
}
}
@@ -140,6 +151,19 @@
}
@Composable
+ fun AllSlices() {
+ val entryRepository by spaEnvironment.entryRepository
+ val authority = spaEnvironment.sliceProviderAuthorities
+ val allSliceEntry =
+ remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
+ RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
+ for (entry in allSliceEntry) {
+ SliceDemo(sliceUri = entry.createSliceUri(authority))
+ }
+ }
+ }
+
+ @Composable
fun OnePage(arguments: Bundle?) {
val context = LocalContext.current
val entryRepository by spaEnvironment.entryRepository
@@ -221,6 +245,18 @@
}
}
+private fun SettingsEntry.createSliceUri(
+ authority: String?,
+ runtimeArguments: Bundle? = null
+): Uri {
+ if (authority == null) return Uri.EMPTY
+ return Uri.Builder().scheme("content").authority(authority).appendSliceParams(
+ route = this.containerPage().buildRoute(),
+ entryId = this.id,
+ runtimeArguments = runtimeArguments,
+ ).build()
+}
+
/**
* A blank activity without any page.
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
new file mode 100644
index 0000000..14855a8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.settingslib.spa.slice
+
+import android.net.Uri
+import android.util.Log
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+
+private const val TAG = "SliceDataRepository"
+
+class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
+ // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
+ private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
+
+ // Note: mark this function synchronized, so that we can get the same livedata during the
+ // whole lifecycle of a Slice.
+ @Synchronized
+ fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
+ val sliceString = sliceUri.getSliceId() ?: return null
+ return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
+ sliceDataMap[sliceString] = it
+ it
+ }
+ }
+
+ fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
+ val sliceString = sliceUri.getSliceId() ?: return null
+ val sliceData = sliceDataMap[sliceString] ?: return null
+ return if (sliceData.isActive()) sliceData else null
+ }
+
+ private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
+ Log.d(TAG, "buildLiveData: $sliceUri")
+
+ val entryId = sliceUri.getEntryId() ?: return null
+ val entry = entryRepository.getEntry(entryId) ?: return null
+ if (!entry.hasSliceSupport) return null
+ val arguments = sliceUri.getRuntimeArguments()
+ return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
new file mode 100644
index 0000000..ff143ed
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.settingslib.spa.slice
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+// Defines SliceUri, which contains special query parameters:
+// -- KEY_DESTINATION: The route that this slice is navigated to.
+// -- KEY_HIGHLIGHT_ENTRY: The entry id of this slice
+// Other parameters can considered as runtime parameters.
+// Use {entryId, runtimeParams} as the unique Id of this Slice.
+typealias SliceUri = Uri
+
+val RESERVED_KEYS = listOf(
+ KEY_DESTINATION,
+ KEY_HIGHLIGHT_ENTRY
+)
+
+fun SliceUri.getEntryId(): String? {
+ return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun SliceUri.getDestination(): String? {
+ return getQueryParameter(KEY_DESTINATION)
+}
+
+fun SliceUri.getRuntimeArguments(): Bundle {
+ val params = Bundle()
+ for (queryName in queryParameterNames) {
+ if (RESERVED_KEYS.contains(queryName)) continue
+ params.putString(queryName, getQueryParameter(queryName))
+ }
+ return params
+}
+
+fun SliceUri.getSliceId(): String? {
+ val entryId = getEntryId() ?: return null
+ val params = getRuntimeArguments()
+ return "${entryId}_$params"
+}
+
+fun Uri.Builder.appendSliceParams(
+ route: String? = null,
+ entryId: String? = null,
+ runtimeArguments: Bundle? = null
+): Uri.Builder {
+ if (route != null) appendQueryParameter(KEY_DESTINATION, route)
+ if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
+ if (runtimeArguments != null) {
+ for (key in runtimeArguments.keySet()) {
+ appendQueryParameter(key, runtimeArguments.getString(key, ""))
+ }
+ }
+ return this
+}
+
+fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val sliceBroadcastClass =
+ SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
+ val entryId = getEntryId() ?: return null
+ return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
+}
+
+fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+ val destination = getDestination() ?: return null
+ val entryId = getEntryId()
+ return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+fun Intent.createBrowsePendingIntent(): PendingIntent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+ val destination = getStringExtra(KEY_DESTINATION) ?: return null
+ val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY)
+ return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+private fun createBrowsePendingIntent(
+ context: Context,
+ browseActivityClass: Class<out Activity>,
+ destination: String,
+ entryId: String?
+): PendingIntent {
+ val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+ .apply {
+ // Set both extra and data (which is a Uri) in Slice Intent:
+ // 1) extra is used in SPA navigation framework
+ // 2) data is used in Slice framework
+ putExtra(KEY_DESTINATION, destination)
+ if (entryId != null) {
+ putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+ }
+ data = Uri.Builder().appendSliceParams(destination, entryId).build()
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+
+ return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+}
+
+private fun createBroadcastPendingIntent(
+ context: Context,
+ sliceBroadcastClass: Class<out BroadcastReceiver>,
+ entryId: String
+): PendingIntent {
+ val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
+ .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() }
+ return PendingIntent.getBroadcast(
+ context, 0 /* requestCode */, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
new file mode 100644
index 0000000..cff1c0c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.settingslib.spa.slice.presenter
+
+import android.net.Uri
+import androidx.compose.material3.Divider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.slice.widget.SliceLiveData
+import androidx.slice.widget.SliceView
+
+@Composable
+fun SliceDemo(sliceUri: Uri) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val sliceData = remember {
+ SliceLiveData.fromUri(context, sliceUri)
+ }
+
+ Divider()
+ AndroidView(
+ factory = { localContext ->
+ val view = SliceView(localContext)
+ view.setShowTitleItems(true)
+ view.isScrollable = false
+ view
+ },
+ update = { view -> sliceData.observe(lifecycleOwner, view) }
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
new file mode 100644
index 0000000..b65b91f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.settingslib.spa.slice.provider
+
+import android.app.PendingIntent
+import android.content.Context
+import android.net.Uri
+import androidx.core.graphics.drawable.IconCompat
+import androidx.slice.Slice
+import androidx.slice.SliceManager
+import androidx.slice.builders.ListBuilder
+import androidx.slice.builders.SliceAction
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.slice.createBroadcastPendingIntent
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+
+fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+ val intent = sliceUri.createBrowsePendingIntent() ?: return null
+ return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+ val intent = sliceUri.createBroadcastPendingIntent() ?: return null
+ return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
+ return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
+ .addRow(ListBuilder.RowBuilder().apply {
+ setPrimaryAction(createSliceAction(context, intent))
+ setTitle(title)
+ setSubtitle(summary)
+ }).build()
+}
+
+private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
+ return SliceAction.create(
+ intent,
+ IconCompat.createWithResource(
+ context,
+ com.google.android.material.R.drawable.navigation_empty_icon
+ ),
+ ListBuilder.ICON_IMAGE,
+ "Enter app"
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 764973f..32b283e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -17,20 +17,13 @@
package com.android.settingslib.spa.widget.scaffold
import androidx.appcompat.R
-import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.FindInPage
-import androidx.compose.material.icons.outlined.MoreVert
-import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.android.settingslib.spa.framework.compose.LocalNavController
@@ -82,27 +75,3 @@
)
}
}
-
-@Composable
-fun MoreOptionsAction(
- content: @Composable ColumnScope.(onDismissRequest: () -> Unit) -> Unit,
-) {
- var expanded by rememberSaveable { mutableStateOf(false) }
- MoreOptionsActionButton { expanded = true }
- val onDismissRequest = { expanded = false }
- DropdownMenu(
- expanded = expanded,
- onDismissRequest = onDismissRequest,
- content = { content(onDismissRequest) },
- )
-}
-
-@Composable
-private fun MoreOptionsActionButton(onClick: () -> Unit) {
- IconButton(onClick) {
- Icon(
- imageVector = Icons.Outlined.MoreVert,
- contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
- )
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
new file mode 100644
index 0000000..7db1ca1
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -0,0 +1,604 @@
+/*
+ * Copyright 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.animation.core.animateTo
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.material3.TopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun CustomizedTopAppBar(
+ title: @Composable () -> Unit,
+ navigationIcon: @Composable () -> Unit = {},
+ actions: @Composable RowScope.() -> Unit = {},
+) {
+ SingleRowTopAppBar(
+ title = title,
+ titleTextStyle = MaterialTheme.typography.titleMedium,
+ navigationIcon = navigationIcon,
+ actions = actions,
+ windowInsets = TopAppBarDefaults.windowInsets,
+ colors = topAppBarColors(),
+ )
+}
+
+/**
+ * The customized LargeTopAppBar for Settings.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun CustomizedLargeTopAppBar(
+ title: String,
+ modifier: Modifier = Modifier,
+ navigationIcon: @Composable () -> Unit = {},
+ actions: @Composable RowScope.() -> Unit = {},
+ scrollBehavior: TopAppBarScrollBehavior? = null,
+) {
+ TwoRowsTopAppBar(
+ title = { Title(title = title, maxLines = 2) },
+ titleTextStyle = MaterialTheme.typography.displaySmall,
+ smallTitleTextStyle = MaterialTheme.typography.titleMedium,
+ titleBottomPadding = LargeTitleBottomPadding,
+ smallTitle = { Title(title = title, maxLines = 1) },
+ modifier = modifier,
+ navigationIcon = navigationIcon,
+ actions = actions,
+ colors = topAppBarColors(),
+ windowInsets = TopAppBarDefaults.windowInsets,
+ maxHeight = 176.dp,
+ pinnedHeight = ContainerHeight,
+ scrollBehavior = scrollBehavior,
+ )
+}
+
+@Composable
+private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
+ Text(
+ text = title,
+ modifier = Modifier
+ .padding(
+ WindowInsets.navigationBars
+ .asPaddingValues()
+ .horizontalValues()
+ )
+ .padding(horizontal = SettingsDimension.itemPaddingAround),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = maxLines,
+ )
+}
+
+@Composable
+private fun topAppBarColors() = TopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background,
+ scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
+ navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
+ titleContentColor = MaterialTheme.colorScheme.onSurface,
+ actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+)
+
+/**
+ * Represents the colors used by a top app bar in different states.
+ * This implementation animates the container color according to the top app bar scroll state. It
+ * does not animate the leading, headline, or trailing colors.
+ */
+@Stable
+private class TopAppBarColors(
+ val containerColor: Color,
+ val scrolledContainerColor: Color,
+ val navigationIconContentColor: Color,
+ val titleContentColor: Color,
+ val actionIconContentColor: Color,
+) {
+
+ /**
+ * Represents the container color used for the top app bar.
+ *
+ * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
+ * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
+ * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
+ *
+ * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
+ * percentage
+ */
+ @Composable
+ fun containerColor(colorTransitionFraction: Float): Color {
+ return lerp(
+ containerColor,
+ scrolledContainerColor,
+ FastOutLinearInEasing.transform(colorTransitionFraction)
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is TopAppBarColors) return false
+
+ if (containerColor != other.containerColor) return false
+ if (scrolledContainerColor != other.scrolledContainerColor) return false
+ if (navigationIconContentColor != other.navigationIconContentColor) return false
+ if (titleContentColor != other.titleContentColor) return false
+ if (actionIconContentColor != other.actionIconContentColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = containerColor.hashCode()
+ result = 31 * result + scrolledContainerColor.hashCode()
+ result = 31 * result + navigationIconContentColor.hashCode()
+ result = 31 * result + titleContentColor.hashCode()
+ result = 31 * result + actionIconContentColor.hashCode()
+
+ return result
+ }
+}
+
+/**
+ * A single-row top app bar that is designed to be called by the small and center aligned top app
+ * bar composables.
+ */
+@Composable
+private fun SingleRowTopAppBar(
+ title: @Composable () -> Unit,
+ titleTextStyle: TextStyle,
+ navigationIcon: @Composable () -> Unit,
+ actions: @Composable (RowScope.() -> Unit),
+ windowInsets: WindowInsets,
+ colors: TopAppBarColors,
+) {
+ // Wrap the given actions in a Row.
+ val actionsRow = @Composable {
+ Row(
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically,
+ content = actions
+ )
+ }
+
+ // Compose a Surface with a TopAppBarLayout content.
+ Surface(color = colors.scrolledContainerColor) {
+ val height = LocalDensity.current.run { ContainerHeight.toPx() }
+ TopAppBarLayout(
+ modifier = Modifier
+ .windowInsetsPadding(windowInsets)
+ // clip after padding so we don't show the title over the inset area
+ .clipToBounds(),
+ heightPx = height,
+ navigationIconContentColor = colors.navigationIconContentColor,
+ titleContentColor = colors.titleContentColor,
+ actionIconContentColor = colors.actionIconContentColor,
+ title = title,
+ titleTextStyle = titleTextStyle,
+ titleAlpha = 1f,
+ titleVerticalArrangement = Arrangement.Center,
+ titleHorizontalArrangement = Arrangement.Start,
+ titleBottomPadding = 0,
+ hideTitleSemantics = false,
+ navigationIcon = navigationIcon,
+ actions = actionsRow,
+ )
+ }
+}
+
+/**
+ * A two-rows top app bar that is designed to be called by the Large and Medium top app bar
+ * composables.
+ *
+ * @throws [IllegalArgumentException] if the given [maxHeight] is equal or smaller than the
+ * [pinnedHeight]
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun TwoRowsTopAppBar(
+ modifier: Modifier = Modifier,
+ title: @Composable () -> Unit,
+ titleTextStyle: TextStyle,
+ titleBottomPadding: Dp,
+ smallTitle: @Composable () -> Unit,
+ smallTitleTextStyle: TextStyle,
+ navigationIcon: @Composable () -> Unit,
+ actions: @Composable RowScope.() -> Unit,
+ windowInsets: WindowInsets,
+ colors: TopAppBarColors,
+ maxHeight: Dp,
+ pinnedHeight: Dp,
+ scrollBehavior: TopAppBarScrollBehavior?
+) {
+ if (maxHeight <= pinnedHeight) {
+ throw IllegalArgumentException(
+ "A TwoRowsTopAppBar max height should be greater than its pinned height"
+ )
+ }
+ val pinnedHeightPx: Float
+ val maxHeightPx: Float
+ val titleBottomPaddingPx: Int
+ LocalDensity.current.run {
+ pinnedHeightPx = pinnedHeight.toPx()
+ maxHeightPx = maxHeight.toPx()
+ titleBottomPaddingPx = titleBottomPadding.roundToPx()
+ }
+
+ // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
+ // visible when collapsed.
+ SideEffect {
+ if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
+ scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
+ }
+ }
+
+ // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
+ // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
+ // collapse.
+ // This will potentially animate or interpolate a transition between the container color and the
+ // container's scrolled color according to the app bar's scroll state.
+ val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
+ val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction))
+
+ // Wrap the given actions in a Row.
+ val actionsRow = @Composable {
+ Row(
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically,
+ content = actions
+ )
+ }
+ val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction)
+ val bottomTitleAlpha = 1f - colorTransitionFraction
+ // Hide the top row title semantics when its alpha value goes below 0.5 threshold.
+ // Hide the bottom row title semantics when the top title semantics are active.
+ val hideTopRowSemantics = colorTransitionFraction < 0.5f
+ val hideBottomRowSemantics = !hideTopRowSemantics
+
+ // Set up support for resizing the top app bar when vertically dragging the bar itself.
+ val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) {
+ Modifier.draggable(
+ orientation = Orientation.Vertical,
+ state = rememberDraggableState { delta ->
+ scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
+ },
+ onDragStopped = { velocity ->
+ settleAppBar(
+ scrollBehavior.state,
+ velocity,
+ scrollBehavior.flingAnimationSpec,
+ scrollBehavior.snapAnimationSpec
+ )
+ }
+ )
+ } else {
+ Modifier
+ }
+
+ Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) {
+ Column {
+ TopAppBarLayout(
+ modifier = Modifier
+ .windowInsetsPadding(windowInsets)
+ // clip after padding so we don't show the title over the inset area
+ .clipToBounds(),
+ heightPx = pinnedHeightPx,
+ navigationIconContentColor = colors.navigationIconContentColor,
+ titleContentColor = colors.titleContentColor,
+ actionIconContentColor = colors.actionIconContentColor,
+ title = smallTitle,
+ titleTextStyle = smallTitleTextStyle,
+ titleAlpha = topTitleAlpha,
+ titleVerticalArrangement = Arrangement.Center,
+ titleHorizontalArrangement = Arrangement.Start,
+ titleBottomPadding = 0,
+ hideTitleSemantics = hideTopRowSemantics,
+ navigationIcon = navigationIcon,
+ actions = actionsRow,
+ )
+ TopAppBarLayout(
+ modifier = Modifier.clipToBounds(),
+ heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
+ ?: 0f),
+ navigationIconContentColor = colors.navigationIconContentColor,
+ titleContentColor = colors.titleContentColor,
+ actionIconContentColor = colors.actionIconContentColor,
+ title = title,
+ titleTextStyle = titleTextStyle,
+ titleAlpha = bottomTitleAlpha,
+ titleVerticalArrangement = Arrangement.Bottom,
+ titleHorizontalArrangement = Arrangement.Start,
+ titleBottomPadding = titleBottomPaddingPx,
+ hideTitleSemantics = hideBottomRowSemantics,
+ navigationIcon = {},
+ actions = {}
+ )
+ }
+ }
+}
+
+/**
+ * The base [Layout] for all top app bars. This function lays out a top app bar navigation icon
+ * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
+ * the actions are optional.
+ *
+ * @param heightPx the total height this layout is capped to
+ * @param navigationIconContentColor the content color that will be applied via a
+ * [LocalContentColor] when composing the navigation icon
+ * @param titleContentColor the color that will be applied via a [LocalContentColor] when composing
+ * the title
+ * @param actionIconContentColor the content color that will be applied via a [LocalContentColor]
+ * when composing the action icons
+ * @param title the top app bar title (header)
+ * @param titleTextStyle the title's text style
+ * @param modifier a [Modifier]
+ * @param titleAlpha the title's alpha
+ * @param titleVerticalArrangement the title's vertical arrangement
+ * @param titleHorizontalArrangement the title's horizontal arrangement
+ * @param titleBottomPadding the title's bottom padding
+ * @param hideTitleSemantics hides the title node from the semantic tree. Apply this
+ * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics
+ * from accessibility services. This is needed to avoid having multiple titles visible to
+ * accessibility services at the same time, when animating between collapsed / expanded states.
+ * @param navigationIcon a navigation icon [Composable]
+ * @param actions actions [Composable]
+ */
+@Composable
+private fun TopAppBarLayout(
+ modifier: Modifier,
+ heightPx: Float,
+ navigationIconContentColor: Color,
+ titleContentColor: Color,
+ actionIconContentColor: Color,
+ title: @Composable () -> Unit,
+ titleTextStyle: TextStyle,
+ titleAlpha: Float,
+ titleVerticalArrangement: Arrangement.Vertical,
+ titleHorizontalArrangement: Arrangement.Horizontal,
+ titleBottomPadding: Int,
+ hideTitleSemantics: Boolean,
+ navigationIcon: @Composable () -> Unit,
+ actions: @Composable () -> Unit,
+) {
+ Layout(
+ {
+ Box(
+ Modifier
+ .layoutId("navigationIcon")
+ .padding(start = TopAppBarHorizontalPadding)
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides navigationIconContentColor,
+ content = navigationIcon
+ )
+ }
+ Box(
+ Modifier
+ .layoutId("title")
+ .padding(horizontal = TopAppBarHorizontalPadding)
+ .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics { } else Modifier)
+ .graphicsLayer(alpha = titleAlpha)
+ ) {
+ ProvideTextStyle(value = titleTextStyle) {
+ CompositionLocalProvider(
+ LocalContentColor provides titleContentColor,
+ content = title
+ )
+ }
+ }
+ Box(
+ Modifier
+ .layoutId("actionIcons")
+ .padding(end = TopAppBarHorizontalPadding)
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides actionIconContentColor,
+ content = actions
+ )
+ }
+ },
+ modifier = modifier
+ ) { measurables, constraints ->
+ val navigationIconPlaceable =
+ measurables.first { it.layoutId == "navigationIcon" }
+ .measure(constraints.copy(minWidth = 0))
+ val actionIconsPlaceable =
+ measurables.first { it.layoutId == "actionIcons" }
+ .measure(constraints.copy(minWidth = 0))
+
+ val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) {
+ constraints.maxWidth
+ } else {
+ (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
+ .coerceAtLeast(0)
+ }
+ val titlePlaceable =
+ measurables.first { it.layoutId == "title" }
+ .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
+
+ // Locate the title's baseline.
+ val titleBaseline =
+ if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) {
+ titlePlaceable[LastBaseline]
+ } else {
+ 0
+ }
+
+ val layoutHeight = heightPx.roundToInt()
+
+ layout(constraints.maxWidth, layoutHeight) {
+ // Navigation icon
+ navigationIconPlaceable.placeRelative(
+ x = 0,
+ y = (layoutHeight - navigationIconPlaceable.height) / 2
+ )
+
+ // Title
+ titlePlaceable.placeRelative(
+ x = when (titleHorizontalArrangement) {
+ Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
+ Arrangement.End ->
+ constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
+ // Arrangement.Start.
+ // A TopAppBarTitleInset will make sure the title is offset in case the
+ // navigation icon is missing.
+ else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
+ },
+ y = when (titleVerticalArrangement) {
+ Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
+ // Apply bottom padding from the title's baseline only when the Arrangement is
+ // "Bottom".
+ Arrangement.Bottom ->
+ if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
+ else layoutHeight - titlePlaceable.height - max(
+ 0,
+ titleBottomPadding - titlePlaceable.height + titleBaseline
+ )
+ // Arrangement.Top
+ else -> 0
+ }
+ )
+
+ // Action icons
+ actionIconsPlaceable.placeRelative(
+ x = constraints.maxWidth - actionIconsPlaceable.width,
+ y = (layoutHeight - actionIconsPlaceable.height) / 2
+ )
+ }
+ }
+}
+
+
+/**
+ * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
+ * after the fling settles.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+private suspend fun settleAppBar(
+ state: TopAppBarState,
+ velocity: Float,
+ flingAnimationSpec: DecayAnimationSpec<Float>?,
+ snapAnimationSpec: AnimationSpec<Float>?
+): Velocity {
+ // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar,
+ // and just return Zero Velocity.
+ // Note that we don't check for 0f due to float precision with the collapsedFraction
+ // calculation.
+ if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
+ return Velocity.Zero
+ }
+ var remainingVelocity = velocity
+ // In case there is an initial velocity that was left after a previous user fling, animate to
+ // continue the motion to expand or collapse the app bar.
+ if (flingAnimationSpec != null && abs(velocity) > 1f) {
+ var lastValue = 0f
+ AnimationState(
+ initialValue = 0f,
+ initialVelocity = velocity,
+ )
+ .animateDecay(flingAnimationSpec) {
+ val delta = value - lastValue
+ val initialHeightOffset = state.heightOffset
+ state.heightOffset = initialHeightOffset + delta
+ val consumed = abs(initialHeightOffset - state.heightOffset)
+ lastValue = value
+ remainingVelocity = this.velocity
+ // avoid rounding errors and stop if anything is unconsumed
+ if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
+ }
+ }
+ // Snap if animation specs were provided.
+ if (snapAnimationSpec != null) {
+ if (state.heightOffset < 0 &&
+ state.heightOffset > state.heightOffsetLimit
+ ) {
+ AnimationState(initialValue = state.heightOffset).animateTo(
+ if (state.collapsedFraction < 0.5f) {
+ 0f
+ } else {
+ state.heightOffsetLimit
+ },
+ animationSpec = snapAnimationSpec
+ ) { state.heightOffset = value }
+ }
+ }
+
+ return Velocity(0f, remainingVelocity)
+}
+
+// An easing function used to compute the alpha value that is applied to the top title part of a
+// Medium or Large app bar.
+private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
+
+private val ContainerHeight = 56.dp
+private val LargeTitleBottomPadding = 28.dp
+private val TopAppBarHorizontalPadding = 4.dp
+
+// A title inset when the App-Bar is a Medium or Large one. Also used to size a spacer when the
+// navigation icon is missing.
+private val TopAppBarTitleInset = 16.dp - TopAppBarHorizontalPadding
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
new file mode 100644
index 0000000..5e201df
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import androidx.appcompat.R
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.MoreVert
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+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.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.res.stringResource
+
+/**
+ * Scope for the children of [MoreOptionsAction].
+ */
+interface MoreOptionsScope : ColumnScope {
+ fun dismiss()
+
+ @Composable
+ fun MenuItem(text: String, enabled: Boolean = true, onClick: () -> Unit) {
+ DropdownMenuItem(
+ text = { Text(text) },
+ onClick = {
+ dismiss()
+ onClick()
+ },
+ enabled = enabled,
+ )
+ }
+}
+
+@Composable
+fun MoreOptionsAction(
+ content: @Composable MoreOptionsScope.() -> Unit,
+) {
+ var expanded by rememberSaveable { mutableStateOf(false) }
+ MoreOptionsActionButton { expanded = true }
+ val onDismiss = { expanded = false }
+ DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
+ val moreOptionsScope = remember(this) {
+ object : MoreOptionsScope, ColumnScope by this {
+ override fun dismiss() {
+ onDismiss()
+ }
+ }
+ }
+ moreOptionsScope.content()
+ }
+}
+
+@Composable
+private fun MoreOptionsActionButton(onClick: () -> Unit) {
+ IconButton(onClick) {
+ Icon(
+ imageVector = Icons.Outlined.MoreVert,
+ contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index efc623a..b4852e4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -24,17 +24,14 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
-import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
@@ -136,7 +133,6 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchTopAppBar(
query: TextFieldValue,
@@ -144,20 +140,16 @@
onClose: () -> Unit,
actions: @Composable RowScope.() -> Unit = {},
) {
- Surface(color = SettingsTheme.colorScheme.surfaceHeader) {
- TopAppBar(
- title = { SearchBox(query, onQueryChange) },
- modifier = Modifier.statusBarsPadding(),
- navigationIcon = { CollapseAction(onClose) },
- actions = {
- if (query.text.isNotEmpty()) {
- ClearAction { onQueryChange(TextFieldValue()) }
- }
- actions()
- },
- colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
- )
- }
+ CustomizedTopAppBar(
+ title = { SearchBox(query, onQueryChange) },
+ navigationIcon = { CollapseAction(onClose) },
+ actions = {
+ if (query.text.isNotEmpty()) {
+ ClearAction { onQueryChange(TextFieldValue()) }
+ }
+ actions()
+ },
+ )
BackHandler { onClose() }
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
index f7cb035..3311792 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
@@ -17,24 +17,9 @@
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.navigationBars
-import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.LargeTopAppBar
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
-import com.android.settingslib.spa.framework.compose.horizontalValues
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.theme.rememberSettingsTypography
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -43,20 +28,12 @@
scrollBehavior: TopAppBarScrollBehavior,
actions: @Composable RowScope.() -> Unit,
) {
- val colorScheme = MaterialTheme.colorScheme
- // TODO: Remove MaterialTheme() after top app bar color fixed in AndroidX.
- MaterialTheme(
- colorScheme = remember { colorScheme.copy(surface = colorScheme.background) },
- typography = rememberSettingsTypography(),
- ) {
- LargeTopAppBar(
- title = { Title(title) },
- navigationIcon = { NavigateBack() },
- actions = actions,
- colors = largeTopAppBarColors(),
- scrollBehavior = scrollBehavior,
- )
- }
+ CustomizedLargeTopAppBar(
+ title = title,
+ navigationIcon = { NavigateBack() },
+ actions = actions,
+ scrollBehavior = scrollBehavior,
+ )
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -65,22 +42,3 @@
heightOffset = heightOffsetLimit
}
}
-
-@Composable
-private fun Title(title: String) {
- Text(
- text = title,
- modifier = Modifier
- .padding(WindowInsets.navigationBars.asPaddingValues().horizontalValues())
- .padding(SettingsDimension.itemPaddingAround),
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun largeTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
- containerColor = MaterialTheme.colorScheme.background,
- scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
-)
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index 2449dec..dcfc171 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -26,6 +26,7 @@
static_libs: [
"SpaLib",
+ "SpaLibTestUtils",
"androidx.test.runner",
"androidx.test.ext.junit",
"androidx.compose.runtime_runtime",
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 5261091..2d501fc 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -21,11 +21,12 @@
android {
namespace 'com.android.settingslib.spa.tests'
- compileSdk spa_target_sdk
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig {
- minSdk spa_min_sdk
- targetSdk spa_target_sdk
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -59,7 +60,7 @@
dependencies {
androidTestImplementation project(":spa")
- androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"
+ androidTestImplementation project(":testutils")
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
androidTestImplementation "com.google.truth:truth:1.1.3"
androidTestImplementation "org.mockito:mockito-android:3.4.6"
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 31d2ae4..2017d53 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -63,7 +63,7 @@
assertThat(entry.toPage).isNull()
assertThat(entry.isAllowSearch).isFalse()
assertThat(entry.isSearchDataDynamic).isFalse()
- assertThat(entry.mutableStatus).isFalse()
+ assertThat(entry.hasMutableStatus).isFalse()
}
@Test
@@ -133,7 +133,7 @@
assertThat(entry.toPage).isNull()
assertThat(entry.isAllowSearch).isTrue()
assertThat(entry.isSearchDataDynamic).isFalse()
- assertThat(entry.mutableStatus).isTrue()
+ assertThat(entry.hasMutableStatus).isTrue()
}
@Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 539e56b..7097a5d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -118,6 +118,7 @@
page.enterPage()
page.leavePage()
page.enterPage()
+ assertThat(page.createBrowseIntent()).isNotNull()
assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
.isEqualTo(2)
assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt
new file mode 100644
index 0000000..019a22e
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import android.content.Context
+import androidx.appcompat.R
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MoreOptionsTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun moreOptionsAction_collapseAtBegin() {
+ composeTestRule.setContent {
+ MoreOptionsAction {
+ MenuItem(text = ITEM_TEXT) {}
+ }
+ }
+
+ composeTestRule.onNodeWithText(ITEM_TEXT).assertDoesNotExist()
+ }
+
+ @Test
+ fun moreOptionsAction_canExpand() {
+ composeTestRule.setContent {
+ MoreOptionsAction {
+ MenuItem(text = ITEM_TEXT) {}
+ }
+ }
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+
+ composeTestRule.onNodeWithText(ITEM_TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun moreOptionsAction_itemClicked() {
+ var menuItemClicked = false
+
+ composeTestRule.setContent {
+ MoreOptionsAction {
+ MenuItem(text = ITEM_TEXT) {
+ menuItemClicked = true
+ }
+ }
+ }
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+ composeTestRule.onNodeWithText(ITEM_TEXT).performClick()
+
+ assertThat(menuItemClicked).isTrue()
+ }
+
+ private companion object {
+ const val ITEM_TEXT = "item text"
+ }
+}
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
new file mode 100644
index 0000000..68ad414
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SpaLibTestUtils",
+
+ srcs: ["src/**/*.kt"],
+
+ static_libs: [
+ "mockito-target-minus-junit4",
+ ],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ ],
+ min_sdk_version: "31",
+}
diff --git a/packages/SettingsLib/Spa/testutils/AndroidManifest.xml b/packages/SettingsLib/Spa/testutils/AndroidManifest.xml
new file mode 100644
index 0000000..1aa7782
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.spa.testutils">
+ <uses-sdk android:minSdkVersion="21"/>
+</manifest>
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
new file mode 100644
index 0000000..3e50b29
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.
+ */
+
+plugins {
+ id 'com.android.library'
+ id 'kotlin-android'
+}
+
+android {
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
+
+ defaultConfig {
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
+ }
+
+ sourceSets {
+ main {
+ kotlin {
+ srcDir "src"
+ }
+ manifest.srcFile "AndroidManifest.xml"
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ freeCompilerArgs = ["-Xjvm-default=all"]
+ }
+}
+
+dependencies {
+ api "org.mockito:mockito-android:3.4.6"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java b/packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
copy to packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt
index 7a2de7b..5ba54c1 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
+++ b/packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt
@@ -14,18 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.settingslib.spa.testutils
-import androidx.annotation.StringRes;
+import org.mockito.Mockito
-import com.android.internal.R;
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
+ * returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any(type)
-/** Helper class for referencing resources */
-class ChooserSelectorResourceHelper {
-
- private ChooserSelectorResourceHelper() {
- }
-
- @StringRes
- static final int CONFIG_CHOOSER_ACTIVITY = R.string.config_chooserActivity;
-}
+inline fun <reified T> any(): T = any(T::class.java)
diff --git a/packages/SettingsLib/Spa/testutils/src/SpaTest.kt b/packages/SettingsLib/Spa/testutils/src/SpaTest.kt
new file mode 100644
index 0000000..a397bb4
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/SpaTest.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.settingslib.spa.testutils
+
+import java.util.concurrent.TimeoutException
+
+/**
+ * Blocks until the given condition is satisfied.
+ */
+fun waitUntil(timeoutMillis: Long = 1000, condition: () -> Boolean) {
+ val startTime = System.currentTimeMillis()
+ while (!condition()) {
+ // Let Android run measure, draw and in general any other async operations.
+ Thread.sleep(10)
+ if (System.currentTimeMillis() - startTime > timeoutMillis) {
+ throw TimeoutException("Condition still not satisfied after $timeoutMillis ms")
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 18ae09ea..4a7418f 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -30,7 +30,6 @@
],
kotlincflags: [
"-Xjvm-default=all",
- "-opt-in=kotlin.RequiresOptIn",
],
}
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..bc88528
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+ <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..c395286
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+ <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
index a7de4ce..b2ea4a0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
@@ -23,7 +23,6 @@
import android.os.UserHandle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
@@ -34,24 +33,25 @@
*/
@Composable
fun DisposableBroadcastReceiverAsUser(
- userId: Int,
intentFilter: IntentFilter,
+ userHandle: UserHandle,
+ onStart: () -> Unit = {},
onReceive: (Intent) -> Unit,
) {
- val broadcastReceiver = remember {
- object : BroadcastReceiver() {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner) {
+ val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
onReceive(intent)
}
}
- }
- val context = LocalContext.current
- val lifecycleOwner = LocalLifecycleOwner.current
- DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
context.registerReceiverAsUser(
- broadcastReceiver, UserHandle.of(userId), intentFilter, null, null)
+ broadcastReceiver, userHandle, intentFilter, null, null
+ )
+ onStart()
} else if (event == Lifecycle.Event.ON_STOP) {
context.unregisterReceiver(broadcastReceiver)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index 373b57f..a7122d0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -54,6 +54,14 @@
)
/**
+ * Gets the group title of this item.
+ *
+ * Note: Items should be sorted by group in [getComparator] first, this [getGroupTitle] will not
+ * change the list order.
+ */
+ fun getGroupTitle(option: Int, record: T): String? = null
+
+ /**
* Gets the summary for the given app record.
*
* @return null if no summary should be displayed.
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 ee89003..487dbcb 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
@@ -21,13 +21,10 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
/**
* The config used to load the App List.
@@ -40,36 +37,42 @@
/**
* The repository to load the App List data.
*/
-internal class AppListRepository(context: Context) {
+internal interface AppListRepository {
+ /** Loads the list of [ApplicationInfo]. */
+ suspend fun loadApps(config: AppListConfig): List<ApplicationInfo>
+
+ /** Gets the flow of predicate that could used to filter system app. */
+ fun showSystemPredicate(
+ userIdFlow: Flow<Int>,
+ showSystemFlow: Flow<Boolean>,
+ ): Flow<(app: ApplicationInfo) -> Boolean>
+}
+
+
+internal class AppListRepositoryImpl(context: Context) : AppListRepository {
private val packageManager = context.packageManager
- fun loadApps(configFlow: Flow<AppListConfig>): Flow<List<ApplicationInfo>> = configFlow
- .map { loadApps(it) }
- .flowOn(Dispatchers.Default)
+ override suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> = coroutineScope {
+ val hiddenSystemModulesDeferred = async {
+ packageManager.getInstalledModules(0)
+ .filter { it.isHidden }
+ .map { it.packageName }
+ .toSet()
+ }
+ val flags = PackageManager.ApplicationInfoFlags.of(
+ (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+ )
+ val installedApplicationsAsUser =
+ packageManager.getInstalledApplicationsAsUser(flags, config.userId)
- private suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> {
- return coroutineScope {
- val hiddenSystemModulesDeferred = async {
- packageManager.getInstalledModules(0)
- .filter { it.isHidden }
- .map { it.packageName }
- .toSet()
- }
- val flags = PackageManager.ApplicationInfoFlags.of(
- (PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
- )
- val installedApplicationsAsUser =
- packageManager.getInstalledApplicationsAsUser(flags, config.userId)
-
- val hiddenSystemModules = hiddenSystemModulesDeferred.await()
- installedApplicationsAsUser.filter { app ->
- app.isInAppList(config.showInstantApps, hiddenSystemModules)
- }
+ val hiddenSystemModules = hiddenSystemModulesDeferred.await()
+ installedApplicationsAsUser.filter { app ->
+ app.isInAppList(config.showInstantApps, hiddenSystemModules)
}
}
- fun showSystemPredicate(
+ override fun showSystemPredicate(
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
): Flow<(app: ApplicationInfo) -> Boolean> =
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 1e487da..650b278 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spaprivileged.model.app
import android.app.Application
+import android.content.Context
import android.content.pm.ApplicationInfo
import android.icu.text.Collator
import androidx.lifecycle.AndroidViewModel
@@ -27,12 +28,16 @@
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
internal data class AppListData<T : AppRecord>(
@@ -43,9 +48,15 @@
AppListData(appEntries.filter(predicate), option)
}
-@OptIn(ExperimentalCoroutinesApi::class)
internal class AppListViewModel<T : AppRecord>(
application: Application,
+) : AppListViewModelImpl<T>(application)
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal open class AppListViewModelImpl<T : AppRecord>(
+ application: Application,
+ appListRepositoryFactory: (Context) -> AppListRepository = ::AppListRepositoryImpl,
+ appRepositoryFactory: (Context) -> AppRepository = ::AppRepositoryImpl,
) : AndroidViewModel(application) {
val appListConfig = StateFlowBridge<AppListConfig>()
val listModel = StateFlowBridge<AppListModel<T>>()
@@ -53,16 +64,18 @@
val option = StateFlowBridge<Int>()
val searchQuery = StateFlowBridge<String>()
- private val appListRepository = AppListRepository(application)
- private val appRepository = AppRepositoryImpl(application)
+ private val appListRepository = appListRepositoryFactory(application)
+ private val appRepository = appRepositoryFactory(application)
private val collator = Collator.getInstance().freeze()
private val labelMap = ConcurrentHashMap<String, String>()
- private val scope = viewModelScope + Dispatchers.Default
+ private val scope = viewModelScope + Dispatchers.IO
private val userIdFlow = appListConfig.flow.map { it.userId }
+ private val appsStateFlow = MutableStateFlow<List<ApplicationInfo>?>(null)
+
private val recordListFlow = listModel.flow
- .flatMapLatest { it.transform(userIdFlow, appListRepository.loadApps(appListConfig.flow)) }
+ .flatMapLatest { it.transform(userIdFlow, appsStateFlow.filterNotNull()) }
.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
private val systemFilteredFlow =
@@ -83,6 +96,12 @@
scheduleOnFirstLoaded()
}
+ fun reloadApps() {
+ viewModelScope.launch {
+ appsStateFlow.value = appListRepository.loadApps(appListConfig.flow.first())
+ }
+ }
+
private fun filterAndSort(option: Int) = listModel.flow.flatMapLatest { listModel ->
listModel.filter(userIdFlow, option, systemFilteredFlow)
.asyncMapItem { record ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 34f12af..90710db 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -22,8 +22,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
+import androidx.compose.ui.res.stringResource
import com.android.settingslib.Utils
import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spaprivileged.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -34,7 +36,12 @@
fun loadLabel(app: ApplicationInfo): String
@Composable
- fun produceLabel(app: ApplicationInfo): State<String>
+ fun produceLabel(app: ApplicationInfo) =
+ produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
+ withContext(Dispatchers.IO) {
+ value = loadLabel(app)
+ }
+ }
@Composable
fun produceIcon(app: ApplicationInfo): State<Drawable?>
@@ -46,13 +53,6 @@
override fun loadLabel(app: ApplicationInfo): String = app.loadLabel(packageManager).toString()
@Composable
- override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
- withContext(Dispatchers.Default) {
- value = app.loadLabel(packageManager).toString()
- }
- }
-
- @Composable
override fun produceIcon(app: ApplicationInfo) =
produceState<Drawable?>(initialValue = null, app) {
withContext(Dispatchers.Default) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 215d22c..cb0bfc6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -26,43 +26,76 @@
private const val TAG = "PackageManagers"
-object PackageManagers {
- private val iPackageManager by lazy { AppGlobals.getPackageManager() }
-
- fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
- getPackageInfoAsUser(packageName, 0, userId)
-
- fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
- PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+interface IPackageManagers {
+ fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo?
+ fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo?
/** Checks whether a package is installed for a given user. */
- fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
+ fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean
+ fun ApplicationInfo.hasRequestPermission(permission: String): Boolean
+
+ /** Checks whether a permission is currently granted to the application. */
+ fun ApplicationInfo.hasGrantPermission(permission: String): Boolean
+
+ suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String>
+ fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo?
+}
+
+object PackageManagers : IPackageManagers by PackageManagersImpl(PackageManagerWrapperImpl)
+
+internal interface PackageManagerWrapper {
+ fun getPackageInfoAsUserCached(
+ packageName: String,
+ flags: Long,
+ userId: Int,
+ ): PackageInfo?
+}
+
+internal object PackageManagerWrapperImpl : PackageManagerWrapper {
+ override fun getPackageInfoAsUserCached(
+ packageName: String,
+ flags: Long,
+ userId: Int,
+ ): PackageInfo? = PackageManager.getPackageInfoAsUserCached(packageName, flags, userId)
+}
+
+internal class PackageManagersImpl(
+ private val packageManagerWrapper: PackageManagerWrapper,
+) : IPackageManagers {
+ private val iPackageManager by lazy { AppGlobals.getPackageManager() }
+
+ override fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
+ getPackageInfoAsUser(packageName, 0, userId)
+
+ override fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
+ PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+
+ override fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
getApplicationInfoAsUser(packageName, userId)?.hasFlag(ApplicationInfo.FLAG_INSTALLED)
?: false
- fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
+ override fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
return packageInfo?.requestedPermissions?.let {
permission in it
} ?: false
}
- fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
+ override fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
- ?: return false
- val index = packageInfo.requestedPermissions.indexOf(permission)
+ val index = packageInfo?.requestedPermissions?.indexOf(permission) ?: return false
return index >= 0 &&
packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED)
}
- suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
+ override suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter {
iPackageManager.isPackageAvailable(it, userId)
}.toSet()
- fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
+ override fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
try {
- PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
+ packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
} catch (e: PackageManager.NameNotFoundException) {
Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 9d6b311..681eb1c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -16,6 +16,9 @@
package com.android.settingslib.spaprivileged.template.app
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@@ -31,8 +34,11 @@
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.ui.CategoryTitle
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
+import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -96,17 +102,28 @@
}
items(count = list.size, key = { option to list[it].record.app.packageName }) {
+ remember(list) { listModel.getGroupTitleIfFirst(option, list, it) }
+ ?.let { group -> CategoryTitle(title = group) }
+
val appEntry = list[it]
val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
- val itemModel = remember(appEntry) {
+ appItem(remember(appEntry) {
AppListItemModel(appEntry.record, appEntry.label, summary)
- }
- appItem(itemModel)
+ })
}
}
}
}
+/** Returns group title if this is the first item of the group. */
+private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
+ option: Int,
+ list: List<AppEntry<T>>,
+ index: Int,
+): String? = getGroupTitle(option, list[index].record)?.takeIf {
+ index == 0 || it != getGroupTitle(option, list[index - 1].record)
+}
+
@Composable
private fun <T : AppRecord> loadAppListData(
config: AppListConfig,
@@ -120,5 +137,15 @@
viewModel.option.Sync(state.option)
viewModel.searchQuery.Sync(state.searchQuery)
- return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
+ DisposableBroadcastReceiverAsUser(
+ intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_CHANGED)
+ addDataScheme("package")
+ },
+ userHandle = UserHandle.of(config.userId),
+ onStart = { viewModel.reloadApps() },
+ ) { viewModel.reloadApps() }
+
+ return viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 388a7d8..f371ce9 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -18,8 +18,6 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -27,6 +25,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
import com.android.settingslib.spa.widget.scaffold.SearchScaffold
import com.android.settingslib.spa.widget.ui.Spinner
import com.android.settingslib.spaprivileged.R
@@ -46,6 +45,7 @@
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
primaryUserOnly: Boolean = false,
+ moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
header: @Composable () -> Unit = {},
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
@@ -53,7 +53,10 @@
SearchScaffold(
title = title,
actions = {
- ShowSystemAction(showSystem.value) { showSystem.value = it }
+ MoreOptionsAction {
+ ShowSystemAction(showSystem.value) { showSystem.value = it }
+ moreOptions()
+ }
},
) { bottomPadding, searchQuery ->
WorkProfilePager(primaryUserOnly) { userInfo ->
@@ -82,15 +85,12 @@
}
@Composable
-private fun ShowSystemAction(showSystem: Boolean, setShowSystem: (showSystem: Boolean) -> Unit) {
- MoreOptionsAction { onDismissRequest ->
- val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system
- DropdownMenuItem(
- text = { Text(stringResource(menuText)) },
- onClick = {
- onDismissRequest()
- setShowSystem(!showSystem)
- },
- )
+private fun MoreOptionsScope.ShowSystemAction(
+ showSystem: Boolean,
+ setShowSystem: (showSystem: Boolean) -> Unit,
+) {
+ val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system
+ MenuItem(text = stringResource(menuText)) {
+ setShowSystem(!showSystem)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index 5afe21e..5cd74e3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -31,6 +31,7 @@
],
static_libs: [
+ "SpaLibTestUtils",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
"androidx.test.ext.junit",
@@ -38,7 +39,4 @@
"mockito-target-minus-junit4",
"truth-prebuilt",
],
- kotlincflags: [
- "-opt-in=kotlin.RequiresOptIn",
- ],
}
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 5d5a24e..bc6925b 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
@@ -24,9 +24,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.toList
-import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -36,11 +33,9 @@
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-
-private const val USER_ID = 0
+import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@@ -80,36 +75,28 @@
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
).thenReturn(emptyList())
- repository = AppListRepository(context)
+ repository = AppListRepositoryImpl(context)
}
@Test
fun notShowInstantApps() = runTest {
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
- val appListFlow = repository.loadApps(flowOf(appListConfig))
+ val appListFlow = repository.loadApps(appListConfig)
- launch {
- val flowValues = mutableListOf<List<ApplicationInfo>>()
- appListFlow.toList(flowValues)
- assertThat(flowValues).hasSize(1)
-
- assertThat(flowValues[0]).containsExactly(normalApp)
- }
+ assertThat(appListFlow).containsExactly(normalApp)
}
@Test
fun showInstantApps() = runTest {
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
- val appListFlow = repository.loadApps(flowOf(appListConfig))
+ val appListFlow = repository.loadApps(appListConfig)
- launch {
- val flowValues = mutableListOf<List<ApplicationInfo>>()
- appListFlow.toList(flowValues)
- assertThat(flowValues).hasSize(1)
+ assertThat(appListFlow).containsExactly(normalApp, instantApp)
+ }
- assertThat(flowValues[0]).containsExactly(normalApp, instantApp)
- }
+ private companion object {
+ const val USER_ID = 0
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
new file mode 100644
index 0000000..b570815
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.app.Application
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.testutils.waitUntil
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class AppListViewModelTest {
+ @JvmField
+ @Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var application: Application
+
+ private val listModel = TestAppListModel()
+
+ private fun createViewModel(): AppListViewModelImpl<TestAppRecord> {
+ val viewModel = AppListViewModelImpl<TestAppRecord>(
+ application = application,
+ appListRepositoryFactory = { FakeAppListRepository },
+ appRepositoryFactory = { FakeAppRepository },
+ )
+ viewModel.appListConfig.setIfAbsent(CONFIG)
+ viewModel.listModel.setIfAbsent(listModel)
+ viewModel.showSystem.setIfAbsent(false)
+ viewModel.option.setIfAbsent(0)
+ viewModel.searchQuery.setIfAbsent("")
+ viewModel.reloadApps()
+ return viewModel
+ }
+
+ @Test
+ fun appListDataFlow() = runTest {
+ val viewModel = createViewModel()
+
+ val (appEntries, option) = viewModel.appListDataFlow.first()
+
+ assertThat(appEntries).hasSize(1)
+ assertThat(appEntries[0].record.app).isSameInstanceAs(APP)
+ assertThat(appEntries[0].label).isEqualTo(LABEL)
+ assertThat(option).isEqualTo(0)
+ }
+
+ @Test
+ fun onFirstLoaded_calledWhenLoaded() = runTest {
+ val viewModel = createViewModel()
+
+ viewModel.appListDataFlow.first()
+
+ waitUntil { listModel.onFirstLoadedCalled }
+ }
+
+ private object FakeAppListRepository : AppListRepository {
+ override suspend fun loadApps(config: AppListConfig) = listOf(APP)
+
+ override fun showSystemPredicate(
+ userIdFlow: Flow<Int>,
+ showSystemFlow: Flow<Boolean>,
+ ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
+ }
+
+ private object FakeAppRepository : AppRepository {
+ override fun loadLabel(app: ApplicationInfo) = LABEL
+
+ @Composable
+ override fun produceIcon(app: ApplicationInfo) = stateOf(null)
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val PACKAGE_NAME = "package.name"
+ const val LABEL = "Label"
+ val CONFIG = AppListConfig(userId = USER_ID, showInstantApps = false)
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+ var onFirstLoadedCalled = false
+
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.asyncMapItem { TestAppRecord(it) }
+
+ @Composable
+ override fun getSummary(option: Int, record: TestAppRecord) = null
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<TestAppRecord>>,
+ ) = recordListFlow
+
+ override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) {
+ onFirstLoadedCalled = true
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
new file mode 100644
index 0000000..6c31f4b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PackageManagersTest {
+
+ private val fakePackageManagerWrapper = FakePackageManagerWrapper()
+
+ private val packageManagersImpl = PackageManagersImpl(fakePackageManagerWrapper)
+
+ @Test
+ fun hasGrantPermission_packageInfoIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = null
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_requestedPermissionsIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_notRequested_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_B)
+ requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED)
+ }
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_notGranted_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+ requestedPermissionsFlags = intArrayOf(0, PackageInfo.REQUESTED_PERMISSION_GRANTED)
+ }
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_granted_returnTrue() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+ requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED, 0)
+ }
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isTrue()
+ }
+
+ private inner class FakePackageManagerWrapper : PackageManagerWrapper {
+ var fakePackageInfo: PackageInfo? = null
+
+ override fun getPackageInfoAsUserCached(
+ packageName: String,
+ flags: Long,
+ userId: Int,
+ ): PackageInfo? = fakePackageInfo
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "packageName"
+ const val PERMISSION_A = "permission.A"
+ const val PERMISSION_B = "permission.B"
+ const val UID = 123
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = UID
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 80c4eac..9f20c78 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -58,26 +58,43 @@
@Test
fun couldShowAppItem() {
- setContent(appEntries = listOf(APP_ENTRY))
+ setContent(appEntries = listOf(APP_ENTRY_A))
- composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+ composeTestRule.onNodeWithText(APP_ENTRY_A.label).assertIsDisplayed()
}
@Test
fun couldShowHeader() {
- setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+ setContent(appEntries = listOf(APP_ENTRY_A), header = { Text(HEADER) })
composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
}
+ @Test
+ fun whenNotGrouped_groupTitleDoesNotExist() {
+ setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = false)
+
+ composeTestRule.onNodeWithText(GROUP_A).assertDoesNotExist()
+ composeTestRule.onNodeWithText(GROUP_B).assertDoesNotExist()
+ }
+
+ @Test
+ fun whenGrouped_groupTitleDisplayed() {
+ setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = true)
+
+ composeTestRule.onNodeWithText(GROUP_A).assertIsDisplayed()
+ composeTestRule.onNodeWithText(GROUP_B).assertIsDisplayed()
+ }
+
private fun setContent(
- header: @Composable () -> Unit = {},
appEntries: List<AppEntry<TestAppRecord>>,
+ header: @Composable () -> Unit = {},
+ enableGrouping: Boolean = false,
) {
composeTestRule.setContent {
AppList(
config = AppListConfig(userId = USER_ID, showInstantApps = false),
- listModel = TestAppListModel(),
+ listModel = TestAppListModel(enableGrouping),
state = AppListState(
showSystem = false.toState(),
option = 0.toState(),
@@ -96,17 +113,37 @@
private companion object {
const val USER_ID = 0
const val HEADER = "Header"
- val APP_ENTRY = AppEntry(
- record = TestAppRecord(ApplicationInfo()),
- label = "AAA",
+ const val GROUP_A = "Group A"
+ const val GROUP_B = "Group B"
+ val APP_ENTRY_A = AppEntry(
+ record = TestAppRecord(
+ app = ApplicationInfo().apply {
+ packageName = "package.name.a"
+ },
+ group = GROUP_A,
+ ),
+ label = "Label A",
+ labelCollationKey = CollationKey("", byteArrayOf()),
+ )
+ val APP_ENTRY_B = AppEntry(
+ record = TestAppRecord(
+ app = ApplicationInfo().apply {
+ packageName = "package.name.b"
+ },
+ group = GROUP_B,
+ ),
+ label = "Label B",
labelCollationKey = CollationKey("", byteArrayOf()),
)
}
}
-private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+private data class TestAppRecord(
+ override val app: ApplicationInfo,
+ val group: String? = null,
+) : AppRecord
-private class TestAppListModel : AppListModel<TestAppRecord> {
+private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> {
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
appListFlow.asyncMapItem { TestAppRecord(it) }
@@ -118,4 +155,7 @@
option: Int,
recordListFlow: Flow<List<TestAppRecord>>,
) = recordListFlow
+
+ override fun getGroupTitle(option: Int, record: TestAppRecord) =
+ if (enableGrouping) record.group else null
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index cec6d7d..b3638c2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -28,7 +28,6 @@
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
import com.android.settingslib.spaprivileged.model.app.userHandle
-import com.google.common.truth.Truth.assertThat
import java.util.UUID
import org.junit.Before
import org.junit.Rule
@@ -77,7 +76,7 @@
}
}
- assertThat(storageSize.value).isEqualTo("123 B")
+ composeTestRule.waitUntil { storageSize.value == "123 B" }
}
companion object {
diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml
new file mode 100644
index 0000000..46abff8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index 327e4e9..8a57232 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -86,7 +86,7 @@
<item msgid="8147982633566548515">"map14"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="2494959071796102843">"Use system selection (default)"</item>
+ <item msgid="2494959071796102843">"Use System Selection (Default)"</item>
<item msgid="4055460186095649420">"SBC"</item>
<item msgid="720249083677397051">"AAC"</item>
<item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -96,7 +96,7 @@
<item msgid="506175145534048710">"Opus"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="8868109554557331312">"Use system selection (default)"</item>
+ <item msgid="8868109554557331312">"Use System Selection (Default)"</item>
<item msgid="9024885861221697796">"SBC"</item>
<item msgid="4688890470703790013">"AAC"</item>
<item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -106,52 +106,52 @@
<item msgid="7940970833006181407">"Opus"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="926809261293414607">"Use system selection (default)"</item>
+ <item msgid="926809261293414607">"Use System Selection (Default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
<item msgid="3208896645474529394">"48.0 kHz"</item>
<item msgid="8420261949134022577">"88.2 kHz"</item>
<item msgid="8887519571067543785">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="2284090879080331090">"Use system selection (default)"</item>
+ <item msgid="2284090879080331090">"Use System Selection (Default)"</item>
<item msgid="1872276250541651186">"44.1 kHz"</item>
<item msgid="8736780630001704004">"48.0 kHz"</item>
<item msgid="7698585706868856888">"88.2 kHz"</item>
<item msgid="8946330945963372966">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="2574107108483219051">"Use system selection (default)"</item>
+ <item msgid="2574107108483219051">"Use System Selection (Default)"</item>
<item msgid="4671992321419011165">"16 bits/sample"</item>
<item msgid="1933898806184763940">"24 bits/sample"</item>
<item msgid="1212577207279552119">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="9196208128729063711">"Use system selection (default)"</item>
+ <item msgid="9196208128729063711">"Use System Selection (Default)"</item>
<item msgid="1084497364516370912">"16 bits/sample"</item>
<item msgid="2077889391457961734">"24 bits/sample"</item>
<item msgid="3836844909491316925">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="3014194562841654656">"Use system selection (default)"</item>
+ <item msgid="3014194562841654656">"Use System Selection (Default)"</item>
<item msgid="5982952342181788248">"Mono"</item>
<item msgid="927546067692441494">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="1997302811102880485">"Use system selection (default)"</item>
+ <item msgid="1997302811102880485">"Use System Selection (Default)"</item>
<item msgid="8005696114958453588">"Mono"</item>
<item msgid="1333279807604675720">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="1241278021345116816">"Optimised for Audio Quality (990kbps/909kbps)"</item>
- <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660 kbps/606 kbps)"</item>
- <item msgid="886408010459747589">"Optimised for Connection Quality (330kbps/303kbps)"</item>
+ <item msgid="1241278021345116816">"Optimized for Audio Quality (990kbps/909kbps)"</item>
+ <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660kbps/606kbps)"</item>
+ <item msgid="886408010459747589">"Optimized for Connection Quality (330kbps/303kbps)"</item>
<item msgid="3808414041654351577">"Best Effort (Adaptive Bit Rate)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="804499336721569838">"Optimised for Audio Quality"</item>
- <item msgid="7451422070435297462">"Balanced Audio and Connection Quality"</item>
- <item msgid="6173114545795428901">"Optimised for Connection Quality"</item>
- <item msgid="4349908264188040530">"Best effort (adaptive bit rate)"</item>
+ <item msgid="804499336721569838">"Optimized for Audio Quality"</item>
+ <item msgid="7451422070435297462">"Balanced Audio And Connection Quality"</item>
+ <item msgid="6173114545795428901">"Optimized for Connection Quality"</item>
+ <item msgid="4349908264188040530">"Best Effort (Adaptive Bit Rate)"</item>
</string-array>
<string-array name="bluetooth_audio_active_device_summaries">
<item msgid="8019740759207729126"></item>
@@ -161,25 +161,25 @@
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="1191094707770726722">"Off"</item>
- <item msgid="7839165897132179888">"64 K"</item>
- <item msgid="2715700596495505626">"256 K"</item>
- <item msgid="7099386891713159947">"1 M"</item>
- <item msgid="6069075827077845520">"4 M"</item>
- <item msgid="6078203297886482480">"8 M"</item>
+ <item msgid="7839165897132179888">"64K"</item>
+ <item msgid="2715700596495505626">"256K"</item>
+ <item msgid="7099386891713159947">"1M"</item>
+ <item msgid="6069075827077845520">"4M"</item>
+ <item msgid="6078203297886482480">"8M"</item>
</string-array>
<string-array name="select_logd_size_lowram_titles">
<item msgid="1145807928339101085">"Off"</item>
- <item msgid="4064786181089783077">"64 K"</item>
- <item msgid="3052710745383602630">"256 K"</item>
- <item msgid="3691785423374588514">"1 M"</item>
+ <item msgid="4064786181089783077">"64K"</item>
+ <item msgid="3052710745383602630">"256K"</item>
+ <item msgid="3691785423374588514">"1M"</item>
</string-array>
<string-array name="select_logd_size_summaries">
<item msgid="409235464399258501">"Off"</item>
- <item msgid="4195153527464162486">"64 K per log buffer"</item>
- <item msgid="7464037639415220106">"256 K per log buffer"</item>
- <item msgid="8539423820514360724">"1 M per log buffer"</item>
- <item msgid="1984761927103140651">"4 M per log buffer"</item>
- <item msgid="2983219471251787208">"8 M per log buffer"</item>
+ <item msgid="4195153527464162486">"64K per log buffer"</item>
+ <item msgid="7464037639415220106">"256K per log buffer"</item>
+ <item msgid="8539423820514360724">"1M per log buffer"</item>
+ <item msgid="1984761927103140651">"4M per log buffer"</item>
+ <item msgid="2983219471251787208">"8M per log buffer"</item>
</string-array>
<string-array name="select_logpersist_titles">
<item msgid="704720725704372366">"Off"</item>
@@ -222,7 +222,7 @@
</string-array>
<string-array name="overlay_display_devices_entries">
<item msgid="4497393944195787240">"None"</item>
- <item msgid="8461943978957133391">"480 p"</item>
+ <item msgid="8461943978957133391">"480p"</item>
<item msgid="6923083594932909205">"480p (secure)"</item>
<item msgid="1226941831391497335">"720p"</item>
<item msgid="7051983425968643928">"720p (secure)"</item>
@@ -258,10 +258,10 @@
<string-array name="app_process_limit_entries">
<item msgid="794656271086646068">"Standard limit"</item>
<item msgid="8628438298170567201">"No background processes"</item>
- <item msgid="915752993383950932">"At most, 1 process"</item>
- <item msgid="8554877790859095133">"At most, 2 processes"</item>
- <item msgid="9060830517215174315">"At most, 3 processes"</item>
- <item msgid="6506681373060736204">"At most, 4 processes"</item>
+ <item msgid="915752993383950932">"At most 1 process"</item>
+ <item msgid="8554877790859095133">"At most 2 processes"</item>
+ <item msgid="9060830517215174315">"At most 3 processes"</item>
+ <item msgid="6506681373060736204">"At most 4 processes"</item>
</string-array>
<string-array name="usb_configuration_titles">
<item msgid="3358668781763928157">"Charging"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 77b20a6..4714a0b 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -46,8 +46,8 @@
<string name="wifi_security_passpoint" msgid="2209078477216565387">"Passpoint"</string>
<string name="wifi_security_sae" msgid="3644520541721422843">"WPA3-Personal"</string>
<string name="wifi_security_psk_sae" msgid="8135104122179904684">"WPA2/WPA3-Personal"</string>
- <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced open"</string>
- <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced open"</string>
+ <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced Open"</string>
+ <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced Open"</string>
<string name="wifi_security_eap_suiteb" msgid="415842785991698142">"WPA3-Enterprise 192-bit"</string>
<string name="wifi_remembered" msgid="3266709779723179188">"Saved"</string>
<string name="wifi_disconnected" msgid="7054450256284661757">"Disconnected"</string>
@@ -59,29 +59,29 @@
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Check password and try again"</string>
<string name="wifi_not_in_range" msgid="1541760821805777772">"Not in range"</string>
<string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
- <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
+ <string name="wifi_no_internet" msgid="1774198889176926299">"No internet access"</string>
<string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
<string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string>
<string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
- <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
+ <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string>
<string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
<string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
- <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
- <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign-in required"</string>
+ <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string>
+ <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign in required"</string>
<string name="wifi_ap_unable_to_handle_new_sta" msgid="5885145407184194503">"Access point temporarily full"</string>
<string name="osu_opening_provider" msgid="4318105381295178285">"Opening <xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g>"</string>
<string name="osu_connect_failed" msgid="9107873364807159193">"Couldn’t connect"</string>
<string name="osu_completing_sign_up" msgid="8412636665040390901">"Completing sign-up…"</string>
- <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again"</string>
+ <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again."</string>
<string name="osu_sign_up_complete" msgid="7640183358878916847">"Sign-up complete. Connecting…"</string>
<string name="speed_label_slow" msgid="6069917670665664161">"Slow"</string>
<string name="speed_label_okay" msgid="1253594383880810424">"OK"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Fast"</string>
- <string name="speed_label_very_fast" msgid="8215718029533182439">"Very fast"</string>
+ <string name="speed_label_very_fast" msgid="8215718029533182439">"Very Fast"</string>
<string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expired"</string>
- <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+ <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Disconnected"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnecting…"</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Connecting…"</string>
@@ -110,8 +110,8 @@
<string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Contacts and call history sharing"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Use for contacts and call history sharing"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Internet connection sharing"</string>
- <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text messages"</string>
- <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
+ <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text Messages"</string>
+ <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Access"</string>
<string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
<string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
@@ -120,14 +120,14 @@
<string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
<string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
- <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string>
+ <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file transfer server"</string>
<string name="bluetooth_map_profile_summary_connected" msgid="4141725591784669181">"Connected to map"</string>
<string name="bluetooth_sap_profile_summary_connected" msgid="1280297388033001037">"Connected to SAP"</string>
- <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file-transfer server"</string>
+ <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file transfer server"</string>
<string name="bluetooth_hid_profile_summary_connected" msgid="3923653977051684833">"Connected to input device"</string>
- <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for Internet access"</string>
- <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local Internet connection with device"</string>
- <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for Internet access"</string>
+ <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for internet access"</string>
+ <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local internet connection with device"</string>
+ <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for internet access"</string>
<string name="bluetooth_map_profile_summary_use_for" msgid="4453622103977592583">"Use for map"</string>
<string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"Use for SIM access"</string>
<string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"Use for media audio"</string>
@@ -146,17 +146,17 @@
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Pairing rejected by <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Computer"</string>
<string name="bluetooth_talkback_headset" msgid="3406852564400882682">"Headset"</string>
- <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Telephone"</string>
+ <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Phone"</string>
<string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
<string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
<string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
<string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
- <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string>
- <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string>
- <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wi-Fi one bar."</string>
- <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
- <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
- <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
+ <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi off."</string>
+ <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi disconnected."</string>
+ <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wifi one bar."</string>
+ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi two bars."</string>
+ <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi three bars."</string>
+ <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi signal full."</string>
<string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
<string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
<string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -178,7 +178,7 @@
<string name="tts_default_rate_title" msgid="3964187817364304022">"Speech rate"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Speed at which the text is spoken"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Pitch"</string>
- <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesised speech"</string>
+ <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesized speech"</string>
<string name="tts_default_lang_title" msgid="4698933575028098940">"Language"</string>
<string name="tts_lang_use_system" msgid="6312945299804012406">"Use system language"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"Language not selected"</string>
@@ -188,7 +188,7 @@
<string name="tts_install_data_title" msgid="1829942496472751703">"Install voice data"</string>
<string name="tts_install_data_summary" msgid="3608874324992243851">"Install the voice data required for speech synthesis"</string>
<string name="tts_engine_security_warning" msgid="3372432853837988146">"This speech synthesis engine may be able to collect all the text that will be spoken, including personal data like passwords and credit card numbers. It comes from the <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> engine. Enable the use of this speech synthesis engine?"</string>
- <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for Text-to-Speech output."</string>
+ <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for text-to-speech output."</string>
<string name="tts_default_sample_string" msgid="6388016028292967973">"This is an example of speech synthesis"</string>
<string name="tts_status_title" msgid="8190784181389278640">"Default language status"</string>
<string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> is fully supported"</string>
@@ -224,7 +224,7 @@
<string name="apn_settings_not_available" msgid="1147111671403342300">"Access Point Name settings are not available for this user"</string>
<string name="enable_adb" msgid="8072776357237289039">"USB debugging"</string>
<string name="enable_adb_summary" msgid="3711526030096574316">"Debug mode when USB is connected"</string>
- <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorisations"</string>
+ <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorizations"</string>
<string name="enable_adb_wireless" msgid="6973226350963971018">"Wireless debugging"</string>
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Debug mode when Wi‑Fi is connected"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Error"</string>
@@ -233,22 +233,22 @@
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Pair device with QR code"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Pair new devices using QR code scanner"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Pair device with pairing code"</string>
- <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six-digit code"</string>
+ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six digit code"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Paired devices"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Currently connected"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Device details"</string>
<string name="adb_device_forget" msgid="193072400783068417">"Forget"</string>
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Device fingerprint: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Connection unsuccessful"</string>
- <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure that <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
+ <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
<string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Pair with device"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi pairing code"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Pairing unsuccessful"</string>
- <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure that the device is connected to the same network."</string>
+ <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure the device is connected to the same network."</string>
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address & Port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -268,28 +268,28 @@
<string name="mock_location_app_set" msgid="4706722469342913843">"Mock location app: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Networking"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"Wireless display certification"</string>
- <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi verbose logging"</string>
+ <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi Verbose Logging"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Wi‑Fi scan throttling"</string>
- <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomisation"</string>
+ <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomization"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Mobile data always active"</string>
<string name="tethering_hardware_offload" msgid="4116053719006939161">"Tethering hardware acceleration"</string>
<string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
<string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
<string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
- <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
+ <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Version"</string>
<string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
- <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
- <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
- <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
+ <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Version"</string>
+ <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP Version"</string>
+ <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Codec"</string>
<string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio Sample Rate"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Trigger Bluetooth Audio Codec\nSelection: Sample Rate"</string>
- <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Grey-out means not supported by phone or headset"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth audio bits per sample"</string>
+ <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Gray-out means not supported by phone or headset"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth Audio Bits Per Sample"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Trigger Bluetooth Audio Codec\nSelection: Bits Per Sample"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth audio channel mode"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth Audio Channel Mode"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"Trigger Bluetooth Audio Codec\nSelection: Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth audio LDAC codec: Playback quality"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"Trigger Bluetooth Audio LDAC\nCodec Selection: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="select_private_dns_configuration_title" msgid="7887550926056143018">"Private DNS"</string>
@@ -301,14 +301,14 @@
<string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Couldn\'t connect"</string>
<string name="wifi_display_certification_summary" msgid="8111151348106907513">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
- <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain and improves network performance"</string>
- <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time that it connects to a network that has MAC randomisation enabled."</string>
+ <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain & improves network performance"</string>
+ <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time it connects to a network that has MAC randomization enabled."</string>
<string name="wifi_metered_label" msgid="8737187690304098638">"Metered"</string>
<string name="wifi_unmetered_label" msgid="6174142840934095093">"Unmetered"</string>
<string name="select_logd_size_title" msgid="1604578195914595173">"Logger buffer sizes"</string>
<string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Select Logger sizes per log buffer"</string>
<string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Clear logger persistent storage?"</string>
- <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we are no longer monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
+ <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we no longer are monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
<string name="select_logpersist_title" msgid="447071974007104196">"Store logger data persistently on device"</string>
<string name="select_logpersist_dialog_title" msgid="7745193591195485594">"Select log buffers to store persistently on device"</string>
<string name="select_usb_configuration_title" msgid="6339801314922294586">"Select USB Configuration"</string>
@@ -319,22 +319,22 @@
<string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Always keep mobile data active, even when Wi‑Fi is active (for fast network switching)."</string>
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Use tethering hardware acceleration if available"</string>
<string name="adb_warning_title" msgid="7708653449506485728">"Allow USB debugging?"</string>
- <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification and read log data."</string>
+ <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
<string name="adbwifi_warning_title" msgid="727104571653031865">"Allow wireless debugging?"</string>
<string name="adbwifi_warning_message" msgid="8005936574322702388">"Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
- <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you\'ve previously authorised?"</string>
+ <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you’ve previously authorized?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"Allow development settings?"</string>
<string name="dev_settings_warning_message" msgid="37741686486073668">"These settings are intended for development use only. They can cause your device and the applications on it to break or misbehave."</string>
<string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verify apps over USB"</string>
- <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
+ <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behavior."</string>
<string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
<string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
- <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the enhanced connectivity feature."</string>
+ <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the Enhanced Connectivity feature."</string>
<string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
<string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
- <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behaviour"</string>
+ <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behavior"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"Debugging"</string>
<string name="debug_app" msgid="8903350241392391766">"Select debug app"</string>
<string name="debug_app_not_set" msgid="1934083001283807188">"No debug application set"</string>
@@ -363,7 +363,7 @@
<string name="debug_hw_overdraw" msgid="8944851091008756796">"Debug GPU overdraw"</string>
<string name="disable_overlays" msgid="4206590799671557143">"Disable HW overlays"</string>
<string name="disable_overlays_summary" msgid="1954852414363338166">"Always use GPU for screen compositing"</string>
- <string name="simulate_color_space" msgid="1206503300335835151">"Simulate colour space"</string>
+ <string name="simulate_color_space" msgid="1206503300335835151">"Simulate color space"</string>
<string name="enable_opengl_traces_title" msgid="4638773318659125196">"Enable OpenGL traces"</string>
<string name="usb_audio_disable_routing" msgid="3367656923544254975">"Disable USB audio routing"</string>
<string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Disable automatic routing to USB audio peripherals"</string>
@@ -379,31 +379,31 @@
<string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Enable GPU debug layers"</string>
<string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Allow loading GPU debug layers for debug apps"</string>
<string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"Enable verbose vendor logging"</string>
- <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery and/or use more storage."</string>
+ <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery, and/or use more storage."</string>
<string name="window_animation_scale_title" msgid="5236381298376812508">"Window animation scale"</string>
<string name="transition_animation_scale_title" msgid="1278477690695439337">"Transition animation scale"</string>
<string name="animator_duration_scale_title" msgid="7082913931326085176">"Animator duration scale"</string>
<string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulate secondary displays"</string>
<string name="debug_applications_category" msgid="5394089406638954196">"Apps"</string>
- <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don\'t keep activities"</string>
+ <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don’t keep activities"</string>
<string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Destroy every activity as soon as the user leaves it"</string>
<string name="app_process_limit_title" msgid="8361367869453043007">"Background process limit"</string>
<string name="show_all_anrs" msgid="9160563836616468726">"Show background ANRs"</string>
- <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialogue for background apps"</string>
+ <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialog for background apps"</string>
<string name="show_notification_channel_warnings" msgid="3448282400127597331">"Show notification channel warnings"</string>
<string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Displays on-screen warning when an app posts a notification without a valid channel"</string>
<string name="force_allow_on_external" msgid="9187902444231637880">"Force allow apps on external"</string>
<string name="force_allow_on_external_summary" msgid="8525425782530728238">"Makes any app eligible to be written to external storage, regardless of manifest values"</string>
- <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizeable"</string>
- <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string>
+ <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizable"</string>
+ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizable for multi-window, regardless of manifest values."</string>
<string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string>
<string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string>
<string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string>
<string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string>
- <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string>
+ <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren’t currently protected"</string>
<string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string>
<string name="local_backup_password_toast_success" msgid="4891666204428091604">"New backup password set"</string>
- <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don\'t match"</string>
+ <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don’t match"</string>
<string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"Failure setting backup password"</string>
<string name="loading_injected_setting_summary" msgid="8394446285689070348">"Loading…"</string>
<string-array name="color_mode_names">
@@ -412,9 +412,9 @@
<item msgid="6564241960833766170">"Standard"</item>
</string-array>
<string-array name="color_mode_descriptions">
- <item msgid="6828141153199944847">"Enhanced colours"</item>
- <item msgid="4548987861791236754">"Natural colours as seen by the eye"</item>
- <item msgid="1282170165150762976">"Colours optimised for digital content"</item>
+ <item msgid="6828141153199944847">"Enhanced colors"</item>
+ <item msgid="4548987861791236754">"Natural colors as seen by the eye"</item>
+ <item msgid="1282170165150762976">"Colors optimized for digital content"</item>
</string-array>
<string name="inactive_apps_title" msgid="5372523625297212320">"Standby apps"</string>
<string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Inactive. Tap to toggle."</string>
@@ -431,15 +431,15 @@
<string name="select_webview_provider_title" msgid="3917815648099445503">"WebView implementation"</string>
<string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Set WebView implementation"</string>
<string name="select_webview_provider_toast_text" msgid="8512254949169359848">"This choice is no longer valid. Try again."</string>
- <string name="picture_color_mode" msgid="1013807330552931903">"Picture colour mode"</string>
+ <string name="picture_color_mode" msgid="1013807330552931903">"Picture color mode"</string>
<string name="picture_color_mode_desc" msgid="151780973768136200">"Use sRGB"</string>
<string name="daltonizer_mode_disabled" msgid="403424372812399228">"Disabled"</string>
<string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Monochromacy"</string>
<string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomaly (red-green)"</string>
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
- <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:<br/> <ol> <li>&nbsp;See colours more accurately</li> <li>&nbsp;Remove colours to help you focus</li> </ol>"</string>
+ <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Color correction can be helpful when you want to:<br/> <ol> <li>&nbsp;See colors more accurately</li> <li>&nbsp;Remove colors to help you focus</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -466,7 +466,7 @@
<string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Device may shut down soon (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
- <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
+ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
@@ -477,9 +477,9 @@
<string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
<string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
- <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
+ <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
- <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
+ <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
<string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
@@ -505,13 +505,13 @@
<string name="active_input_method_subtypes" msgid="4232680535471633046">"Active input methods"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Use system languages"</string>
<string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
- <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
+ <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
<string name="ims_reg_title" msgid="8197592958123671062">"IMS registration state"</string>
<string name="ims_reg_status_registered" msgid="884916398194885457">"Registered"</string>
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
- <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
+ <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string>
<string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
@@ -520,7 +520,7 @@
<string name="done" msgid="381184316122520313">"Done"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarms and reminders"</string>
<string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Allow setting alarms and reminders"</string>
- <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms and reminders"</string>
+ <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms & reminders"</string>
<string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"Allow this app to set alarms and schedule time-sensitive actions. This lets the app run in the background, which may use more battery.\n\nIf this permission is off, existing alarms and time-based events scheduled by this app won’t work."</string>
<string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"schedule, alarm, reminder, clock"</string>
<string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Turn on"</string>
@@ -539,7 +539,7 @@
<string name="media_transfer_this_device_name" product="default" msgid="2357329267148436433">"This phone"</string>
<string name="media_transfer_this_device_name" product="tablet" msgid="3714653244000242800">"This tablet"</string>
<string name="media_transfer_this_phone" msgid="7194341457812151531">"This phone"</string>
- <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
+ <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off & back on"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
<string name="storage_category" msgid="2287342585424631813">"Storage"</string>
@@ -548,23 +548,23 @@
<string name="shared_data_no_blobs_text" msgid="3108114670341737434">"There is no shared data for this user."</string>
<string name="shared_data_query_failure_text" msgid="3489828881998773687">"There was an error fetching shared data. Try again."</string>
<string name="blob_id_text" msgid="8680078988996308061">"Shared data ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string>
- <string name="blob_expires_text" msgid="7882727111491739331">"Expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+ <string name="blob_expires_text" msgid="7882727111491739331">"Expires at <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="shared_data_delete_failure_text" msgid="3842701391009628947">"There was an error deleting the shared data."</string>
<string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"There are no leases acquired for this shared data. Would you like to delete it?"</string>
<string name="accessor_info_title" msgid="8289823651512477787">"Apps sharing data"</string>
<string name="accessor_no_description_text" msgid="7510967452505591456">"No description provided by the app."</string>
- <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+ <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires at <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="delete_blob_text" msgid="2819192607255625697">"Delete shared data"</string>
- <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure that you want to delete this shared data?"</string>
+ <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure you want to delete this shared data?"</string>
<string name="user_add_user_item_summary" msgid="5748424612724703400">"Users have their own apps and content"</string>
<string name="user_add_profile_item_summary" msgid="5418602404308968028">"You can restrict access to apps and content from your account"</string>
<string name="user_add_user_item_title" msgid="2394272381086965029">"User"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Restricted profile"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Add new user?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customise with apps, wallpaper and so on. Users can also adjust device settings such as Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Set up user now?"</string>
- <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure that the person is available to take the device and set up their space."</string>
+ <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure the person is available to take the device and set up their space"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Set up profile now?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"Set up now"</string>
<string name="user_setup_button_setup_later" msgid="8712980133555493516">"Not now"</string>
@@ -573,7 +573,7 @@
<string name="user_new_profile_name" msgid="2405500423304678841">"New profile"</string>
<string name="user_info_settings_title" msgid="6351390762733279907">"User info"</string>
<string name="profile_info_settings_title" msgid="105699672534365099">"Profile info"</string>
- <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
+ <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you’ll need to set up a screen lock to protect your apps and personal data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
@@ -616,7 +616,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
- <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"On"</string>
<string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Off"</string>
<string name="carrier_network_change_mode" msgid="4257621815706644026">"Carrier network changing"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 29d7d95..cd1e7d1 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -610,7 +610,7 @@
<string name="user_image_photo_selector" msgid="433658323306627093">"Hautatu argazki bat"</string>
<string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"Saiakera oker gehiegi egin dituzu. Gailu honetako datuak ezabatu egingo dira."</string>
<string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"Saiakera oker gehiegi egin dituzu. Erabiltzailea ezabatu egingo da."</string>
- <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Saiakera oker gehiegi egin dituzu. Laneko profila eta bertako datuak ezabatu egingo dira."</string>
+ <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Saiakera oker gehiegi egin dituzu. Laneko profila eta bertako datuak ezabatuko dira."</string>
<string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"Baztertu"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Gailuaren balio lehenetsia"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desgaituta"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 1c4e4a0..b1ef50a 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -174,7 +174,7 @@
<string name="launch_defaults_some" msgid="3631650616557252926">"कुछ डिफ़ॉल्ट सेट हैं"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"कोई डिफ़ॉल्ट सेट नहीं है"</string>
<string name="tts_settings" msgid="8130616705989351312">"लेख से बोली सेटिंग"</string>
- <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलना"</string>
+ <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलने की सुविधा"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"बोली दर"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"बोलने की गति तय करें"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"पिच"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index 7e65fa0..928ebc3 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -49,9 +49,9 @@
<item msgid="1999413958589971747">"Unngår dårlig tilkobling midlertidig"</item>
</string-array>
<string-array name="hdcp_checking_titles">
- <item msgid="2377230797542526134">"Kontrollér aldri"</item>
- <item msgid="3919638466823112484">"Kontrollér kun DRM-innhold"</item>
- <item msgid="9048424957228926377">"Kontrollér alltid"</item>
+ <item msgid="2377230797542526134">"Kontroller aldri"</item>
+ <item msgid="3919638466823112484">"Kontroller kun DRM-innhold"</item>
+ <item msgid="9048424957228926377">"Kontroller alltid"</item>
</string-array>
<string-array name="hdcp_checking_summaries">
<item msgid="4045840870658484038">"Bruk aldri HDCP-kontroll"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index c292e88..59a2517 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -427,7 +427,7 @@
<string name="transcode_notification" msgid="5560515979793436168">"Vis omkodingsvarsler"</string>
<string name="transcode_disable_cache" msgid="3160069309377467045">"Slå av omkodingsbuffer"</string>
<string name="runningservices_settings_title" msgid="6460099290493086515">"Aktive tjenester"</string>
- <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontrollér tjenester som kjører"</string>
+ <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontroller tjenester som kjører"</string>
<string name="select_webview_provider_title" msgid="3917815648099445503">"WebView-implementering"</string>
<string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Angi WebView-implementering"</string>
<string name="select_webview_provider_toast_text" msgid="8512254949169359848">"Dette valget er ikke gyldig lenger. Prøv på nytt."</string>
diff --git a/packages/SettingsLib/res/values/carrierid_icon_overrides.xml b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml
new file mode 100644
index 0000000..d2ae52d
--- /dev/null
+++ b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<!--
+ ~ This resource file exists to enumerate all network type icon overrides on a
+ ~ per-carrierId basis
+-->
+<resources>
+ <!--
+ Network type (RAT) icon overrides can be configured here on a per-carrierId basis.
+ 1. Add a new TypedArray here, using the naming scheme below
+ 2. The entries are (NetworkType, drawable ID) pairs
+ 3. Add this array's ID to the MAPPING field of MobileIconCarrierIdOverrides.kt
+ -->
+ <array name="carrierId_2032_iconOverrides">
+ <item>5G_PLUS</item>
+ <item>@drawable/ic_5g_plus_mobiledata_default</item>
+ </array>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 7913c16..65c94ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -36,8 +36,10 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -1577,8 +1579,8 @@
public long internalSize;
public long externalSize;
public String labelDescription;
-
public boolean mounted;
+ public boolean showInPersonalTab;
/**
* Setting this to {@code true} prevents the entry to be filtered by
@@ -1635,6 +1637,33 @@
ThreadUtils.postOnBackgroundThread(
() -> this.ensureLabelDescriptionLocked(context));
}
+ this.showInPersonalTab = shouldShowInPersonalTab(context, info.uid);
+ }
+
+ /**
+ * Checks if the user that the app belongs to have the property
+ * {@link UserProperties#SHOW_IN_SETTINGS_WITH_PARENT} set.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ boolean shouldShowInPersonalTab(Context context, int uid) {
+ UserManager userManager = UserManager.get(context);
+ int userId = UserHandle.getUserId(uid);
+
+ // Regardless of apk version, if the app belongs to the current user then return true.
+ if (userId == ActivityManager.getCurrentUser()) {
+ return true;
+ }
+
+ // For sdk version < 34, if the app doesn't belong to the current user,
+ // then as per earlier behaviour the app shouldn't be displayed in personal tab.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return false;
+ }
+
+ UserProperties userProperties = userManager.getUserProperties(
+ UserHandle.of(userId));
+ return userProperties.getShowInSettings()
+ == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT;
}
public void ensureLabel(Context context) {
@@ -1784,7 +1813,7 @@
@Override
public boolean filterApp(AppEntry entry) {
- return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
+ return entry.showInPersonalTab;
}
};
@@ -1811,7 +1840,7 @@
@Override
public boolean filterApp(AppEntry entry) {
- return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
+ return !entry.showInPersonalTab;
}
};
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
index 6ce72bb..3af64e2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
@@ -82,4 +82,4 @@
Log.e(TAG, "localBluetoothAdapter is NULL!!");
}
}
-};
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 950ee21..9583a59 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -668,6 +668,12 @@
* @param bluetoothProfile the Bluetooth profile
*/
public void onActiveDeviceChanged(boolean isActive, int bluetoothProfile) {
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "onActiveDeviceChanged: "
+ + "profile " + BluetoothProfile.getProfileName(bluetoothProfile)
+ + ", device " + mDevice.getAnonymizedAddress()
+ + ", isActive " + isActive);
+ }
boolean changed = false;
switch (bluetoothProfile) {
case BluetoothProfile.A2DP:
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index 3e33da5..ece8986 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -50,6 +50,34 @@
@Override
public void clicked(int sourceCategory, String key) {
+ final LogMaker logMaker = new LogMaker(MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ if (sourceCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceCategory);
+ }
+ if (!TextUtils.isEmpty(key)) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+ key);
+ }
+ MetricsLogger.action(logMaker);
+ }
+
+ @Override
+ public void changed(int category, String key, int value) {
+ final LogMaker logMaker = new LogMaker(
+ MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ if (category != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, category);
+ }
+ if (!TextUtils.isEmpty(key)) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+ key);
+ logMaker.addTaggedData(
+ MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ value);
+ }
+ MetricsLogger.action(logMaker);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index cceca13..dcd6cce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -39,6 +39,11 @@
void clicked(int category, String key);
/**
+ * Logs a value changed event when user changed item value.
+ */
+ void changed(int category, String key, int value);
+
+ /**
* Logs an user action.
*/
void action(Context context, int category, Pair<Integer, Object>... taggedData);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 915421a..09abc39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -108,6 +108,19 @@
}
/**
+ * Logs a value changed event when user changed item value.
+ *
+ * @param category the target page id
+ * @param key the key id that user clicked
+ * @param value the value that user changed which converted to integer
+ */
+ public void changed(int category, String key, int value) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.changed(category, key, value);
+ }
+ }
+
+ /**
* Logs a simple action without page id or attribution
*
* @param category the target page
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 869de0de..067afa4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -35,15 +35,22 @@
private static final String LOG_TAG = "SharedPreferencesLogger";
private final String mTag;
+ private final int mMetricCategory;
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeature;
private final Set<String> mPreferenceKeySet;
public SharedPreferencesLogger(Context context, String tag,
MetricsFeatureProvider metricsFeature) {
+ this(context, tag, metricsFeature, SettingsEnums.PAGE_UNKNOWN);
+ }
+
+ public SharedPreferencesLogger(Context context, String tag,
+ MetricsFeatureProvider metricsFeature, int metricCategory) {
mContext = context;
mTag = tag;
mMetricsFeature = metricsFeature;
+ mMetricCategory = metricCategory;
mPreferenceKeySet = new ConcurrentSkipListSet<>();
}
@@ -151,20 +158,15 @@
return;
}
// Pref key exists in set, log its change in metrics.
- mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- prefKey,
- intVal);
+ mMetricsFeature.changed(mMetricCategory, key, intVal);
}
@VisibleForTesting
void logPackageName(String key, String value) {
- final String prefKey = mTag + "/" + key;
- mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
+ mMetricsFeature.action(mMetricCategory,
SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
SettingsEnums.PAGE_UNKNOWN,
- prefKey + ":" + value,
+ key + ":" + value,
0);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 34da305..3e710e4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -24,7 +24,6 @@
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
-import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -221,17 +220,14 @@
}
/**
- * Reverts battery saver schedule mode to none if we are in a bad state where routine mode
- * is selected but no app is configured to actually provide the signal.
+ * Reverts battery saver schedule mode to none if routine mode is selected.
* @param context a valid context
*/
public static void revertScheduleToNoneIfNeeded(Context context) {
ContentResolver resolver = context.getContentResolver();
final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
- boolean providerConfigured = !TextUtils.isEmpty(context.getString(
- com.android.internal.R.string.config_batterySaverScheduleProvider));
- if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC && !providerConfigured) {
+ if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC) {
Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index 5fa04f9..faea5b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -412,14 +412,13 @@
}
companion object {
- private const val TAG = "ThemedBatteryDrawable"
- private const val WIDTH = 12f
- private const val HEIGHT = 20f
+ const val WIDTH = 12f
+ const val HEIGHT = 20f
private const val CRITICAL_LEVEL = 15
// On a 12x20 grid, how wide to make the fill protection stroke.
// Scales when our size changes
private const val PROTECTION_STROKE_WIDTH = 3f
// Arbitrarily chosen for visibility at small sizes
- private const val PROTECTION_MIN_STROKE_WIDTH = 6f
+ const val PROTECTION_MIN_STROKE_WIDTH = 6f
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index c829bc3..3ba51d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -293,7 +293,7 @@
return false;
}
setConnectedRecord();
- mRouterManager.selectRoute(mPackageName, mRouteInfo);
+ mRouterManager.transfer(mPackageName, mRouteInfo);
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt
new file mode 100644
index 0000000..a0395b5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.settingslib.mobile
+
+import android.annotation.DrawableRes
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.R
+import com.android.settingslib.SignalIcon.MobileIconGroup
+
+/**
+ * This class defines a network type (3G, 4G, etc.) override mechanism on a per-carrierId basis.
+ *
+ * Traditionally, carrier-customized network type iconography was achieved using the `MCC/MNC`
+ * resource qualifiers, and swapping out the drawable resource by name. It would look like this:
+ *
+ * res/
+ * drawable/
+ * 3g_mobiledata_icon.xml
+ * drawable-MCC-MNC/
+ * 3g_mobiledata_icon.xml
+ *
+ * This would mean that, provided a context created with this MCC/MNC configuration set, loading
+ * the network type icon through [MobileIconGroup] would provide a carrier-defined network type
+ * icon rather than the AOSP-defined default.
+ *
+ * The MCC/MNC mechanism no longer can fully define carrier-specific network type icons, because
+ * there is no longer a 1:1 mapping between MCC/MNC and carrier. With the advent of MVNOs, multiple
+ * carriers can have the same MCC/MNC value, but wish to differentiate based on their carrier ID.
+ * CarrierId is a newer concept than MCC/MNC, and provides more granularity when it comes to
+ * determining the carrier (e.g. MVNOs can share MCC/MNC values with the network owner), therefore
+ * it can fit all of the same use cases currently handled by `MCC/MNC`, without the need to apply a
+ * configuration context in order to get the proper UI for a given SIM icon.
+ *
+ * NOTE: CarrierId icon overrides will always take precedence over those defined using `MCC/MNC`
+ * resource qualifiers.
+ *
+ * [MAPPING] encodes the relationship between CarrierId and the corresponding override array
+ * that exists in the config.xml. An alternative approach could be to generate the resource name
+ * by string concatenation at run-time:
+ *
+ * val resName = "carrierId_$carrierId_iconOverrides"
+ * val override = resources.getResourceIdentifier(resName)
+ *
+ * However, that's going to be far less efficient until MAPPING grows to a sufficient size. For now,
+ * given a relatively small number of entries, we should just maintain the mapping here.
+ */
+interface MobileIconCarrierIdOverrides {
+ @DrawableRes
+ fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int
+ fun carrierIdEntryExists(carrierId: Int): Boolean
+}
+
+class MobileIconCarrierIdOverridesImpl : MobileIconCarrierIdOverrides {
+ @DrawableRes
+ override fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int {
+ val resId = MAPPING[carrierId] ?: return 0
+ val ta = resources.obtainTypedArray(resId)
+ val map = parseNetworkIconOverrideTypedArray(ta)
+ ta.recycle()
+ return map[networkType] ?: 0
+ }
+
+ override fun carrierIdEntryExists(carrierId: Int) =
+ overrideExists(carrierId, MAPPING)
+
+ companion object {
+ private const val TAG = "MobileIconOverrides"
+ /**
+ * This map maintains the lookup from the canonical carrier ID (see below link) to the
+ * corresponding overlay resource. New overrides should add an entry below in order to
+ * change the network type icon resources based on carrier ID
+ *
+ * Refer to the link below for the canonical mapping maintained in AOSP:
+ * https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb
+ */
+ private val MAPPING = mapOf(
+ // 2032 == Xfinity Mobile
+ 2032 to R.array.carrierId_2032_iconOverrides,
+ )
+
+ /**
+ * Parse `carrierId_XXXX_iconOverrides` for a particular network type. The resource file
+ * "carrierid_icon_overrides.xml" defines a TypedArray format for overriding specific
+ * network type icons (a.k.a. RAT icons) for a particular carrier ID. The format is defined
+ * as an array of (network type name, drawable) pairs:
+ * <array name="carrierId_XXXX_iconOverrides>
+ * <item>NET_TYPE_1</item>
+ * <item>@drawable/net_type_1_override</item>
+ * <item>NET_TYPE_2</item>
+ * <item>@drawable/net_type_2_override</item>
+ * </array>
+ *
+ * @param ta the [TypedArray] defined in carrierid_icon_overrides.xml
+ * @return the overridden drawable resource ID if it exists, or 0 if it does not
+ */
+ @VisibleForTesting
+ @JvmStatic
+ fun parseNetworkIconOverrideTypedArray(ta: TypedArray): Map<String, Int> {
+ if (ta.length() % 2 != 0) {
+ Log.w(TAG,
+ "override must contain an even number of (key, value) entries. skipping")
+
+ return mapOf()
+ }
+
+ val result = mutableMapOf<String, Int>()
+ // The array is defined as Pair(String, resourceId), so walk by 2
+ for (i in 0 until ta.length() step 2) {
+ val key = ta.getString(i)
+ val override = ta.getResourceId(i + 1, 0)
+ if (key == null || override == 0) {
+ Log.w(TAG, "Invalid override found. Skipping")
+ continue
+ }
+ result[key] = override
+ }
+
+ return result
+ }
+
+ @JvmStatic
+ private fun overrideExists(carrierId: Int, mapping: Map<Int, Int>): Boolean =
+ mapping.containsKey(carrierId)
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index f1e1e7d..c5598bf 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -293,4 +293,15 @@
assertThat(ApplicationsState.FILTER_MOVIES.filterApp(mEntry)).isFalse();
}
+
+ @Test
+ public void testPersonalAndWorkFiltersDisplaysCorrectApps() {
+ mEntry.showInPersonalTab = true;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
+ assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isFalse();
+
+ mEntry.showInPersonalTab = false;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+ assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isTrue();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index fc2bf0a..39875f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -17,6 +17,7 @@
package com.android.settingslib.applications;
import static android.os.UserHandle.MU_ENABLED;
+import static android.os.UserHandle.USER_SYSTEM;
import static com.google.common.truth.Truth.assertThat;
@@ -48,9 +49,11 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -58,6 +61,8 @@
import android.text.TextUtils;
import android.util.IconDrawableFactory;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
import com.android.settingslib.applications.ApplicationsState.Session;
@@ -71,6 +76,7 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -79,6 +85,7 @@
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContextImpl;
import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Arrays;
@@ -95,6 +102,7 @@
private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable";
private static final int PROFILE_USERID = 10;
+ private static final int PROFILE_USERID2 = 11;
private static final String PKG_1 = "PKG1";
private static final int OWNER_UID_1 = 1001;
@@ -106,6 +114,10 @@
private static final String PKG_3 = "PKG3";
private static final int OWNER_UID_3 = 1003;
+ private static final int PROFILE_UID_3 = UserHandle.getUid(PROFILE_USERID2, OWNER_UID_3);
+
+ private static final String CLONE_USER = "clone_user";
+ private static final String RANDOM_USER = "random_user";
/** Class under test */
private ApplicationsState mApplicationsState;
@@ -113,6 +125,8 @@
private Application mApplication;
+ @Spy
+ Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private Callbacks mCallbacks;
@Captor
@@ -738,4 +752,51 @@
when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false);
mApplicationsState.setInterestingConfigChanges(configChanges);
}
+
+ @Test
+ public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() {
+ ApplicationInfo appInfo = createApplicationInfo(PKG_1);
+ AppEntry primaryUserApp = createAppEntry(appInfo, 1);
+
+ assertThat(primaryUserApp.shouldShowInPersonalTab(mContext, appInfo.uid)).isTrue();
+ }
+
+ @Test
+ public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
+ ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+ Build.VERSION_CODES.TIRAMISU);
+ // Create an app (and subsequent AppEntry) in a non-primary user profile.
+ ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
+ AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1);
+
+ assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(mContext, appInfo1.uid)).isFalse();
+ }
+
+ @Test
+ public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() {
+ // Mark system user as parent for both profile users.
+ ShadowUserManager shadowUserManager = Shadow
+ .extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+ shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID,
+ CLONE_USER, 0);
+ shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2,
+ RANDOM_USER, 0);
+ shadowUserManager.setupUserProperty(PROFILE_USERID,
+ /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_WITH_PARENT);
+ shadowUserManager.setupUserProperty(PROFILE_USERID2,
+ /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_NO);
+
+ ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ // Treat PROFILE_USERID as a clone user profile and create an app PKG_1 in it.
+ ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
+ // Treat PROFILE_USERID2 as a random non-primary profile and create an app PKG_3 in it.
+ ApplicationInfo appInfo2 = createApplicationInfo(PKG_3, PROFILE_UID_3);
+ AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1);
+ AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2);
+
+ assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(mContext, appInfo1.uid)).isTrue();
+ assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(mContext, appInfo2.uid)).isFalse();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
index 89de81f..a2b208a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -39,7 +39,6 @@
private static final String TEST_TAG = "tag";
private static final String TEST_KEY = "key";
- private static final String TEST_TAGGED_KEY = TEST_TAG + "/" + TEST_KEY;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@@ -65,10 +64,8 @@
editor.putInt(TEST_KEY, 2);
editor.putInt(TEST_KEY, 2);
- verify(mMetricsFeature, times(6)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(6)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -82,15 +79,11 @@
editor.putBoolean(TEST_KEY, false);
- verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY,
+ verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY,
1);
- verify(mMetricsFeature, times(3)).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY,
+ verify(mMetricsFeature, times(3)).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY,
0);
}
@@ -103,10 +96,8 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, 2);
- verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -117,10 +108,8 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, veryBigNumber);
- verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY,
+ verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY,
Integer.MAX_VALUE);
}
@@ -131,10 +120,8 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, veryNegativeNumber);
- verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY, Integer.MIN_VALUE);
+ verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY, Integer.MIN_VALUE);
}
@Test
@@ -146,10 +133,8 @@
editor.putFloat(TEST_KEY, 1);
editor.putFloat(TEST_KEY, 2);
- verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -159,7 +144,7 @@
verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
ACTION_SETTINGS_PREFERENCE_CHANGE,
SettingsEnums.PAGE_UNKNOWN,
- "tag/key:com.android.settings",
+ "key:com.android.settings",
0);
}
@@ -170,10 +155,8 @@
mSharedPrefLogger.logValue(TEST_KEY, "62");
mSharedPrefLogger.logValue(TEST_KEY, "0");
- verify(mMetricsFeature, times(3)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(3)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -185,10 +168,8 @@
mSharedPrefLogger.logValue(TEST_KEY, "4.2");
mSharedPrefLogger.logValue(TEST_KEY, "3.0");
- verify(mMetricsFeature, times(0)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(0)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 5399f8a..c058a61 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -461,7 +461,7 @@
public void connect_shouldSelectRoute() {
mInfoMediaDevice1.connect();
- verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1);
+ verify(mMediaRouter2Manager).transfer(TEST_PACKAGE_NAME, mRouteInfo1);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java
new file mode 100644
index 0000000..740261d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.settingslib.mobile;
+
+import static com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl.parseNetworkIconOverrideTypedArray;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.TypedArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public final class MobileIconCarrierIdOverridesTest {
+ private static final String OVERRIDE_ICON_1_NAME = "name_1";
+ private static final int OVERRIDE_ICON_1_RES = 1;
+
+ private static final String OVERRIDE_ICON_2_NAME = "name_2";
+ private static final int OVERRIDE_ICON_2_RES = 2;
+
+ NetworkOverrideTypedArrayMock mResourceMock;
+
+ @Before
+ public void setUp() {
+ mResourceMock = new NetworkOverrideTypedArrayMock(
+ new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME },
+ new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES }
+ );
+ }
+
+ @Test
+ public void testParse_singleOverride() {
+ mResourceMock.setOverrides(
+ new String[] { OVERRIDE_ICON_1_NAME },
+ new int[] { OVERRIDE_ICON_1_RES }
+ );
+
+ Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+ assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES);
+ }
+
+ @Test
+ public void testParse_multipleOverrides() {
+ mResourceMock.setOverrides(
+ new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME },
+ new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES }
+ );
+
+ Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+ assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isEqualTo(OVERRIDE_ICON_2_RES);
+ assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES);
+ }
+
+ @Test
+ public void testParse_nonexistentKey_isNull() {
+ mResourceMock.setOverrides(
+ new String[] { OVERRIDE_ICON_1_NAME },
+ new int[] { OVERRIDE_ICON_1_RES }
+ );
+
+ Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+ assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isNull();
+ }
+
+ static class NetworkOverrideTypedArrayMock {
+ private Object[] mInterleaved;
+
+ private final TypedArray mMockTypedArray = mock(TypedArray.class);
+
+ NetworkOverrideTypedArrayMock(
+ String[] networkTypes,
+ int[] iconOverrides) {
+
+ mInterleaved = interleaveTypes(networkTypes, iconOverrides);
+
+ doAnswer(invocation -> {
+ return mInterleaved[(int) invocation.getArgument(0)];
+ }).when(mMockTypedArray).getString(/* index */ anyInt());
+
+ doAnswer(invocation -> {
+ return mInterleaved[(int) invocation.getArgument(0)];
+ }).when(mMockTypedArray).getResourceId(/* index */ anyInt(), /* default */ anyInt());
+
+ when(mMockTypedArray.length()).thenAnswer(invocation -> {
+ return mInterleaved.length;
+ });
+ }
+
+ TypedArray getMock() {
+ return mMockTypedArray;
+ }
+
+ void setOverrides(String[] types, int[] resIds) {
+ mInterleaved = interleaveTypes(types, resIds);
+ }
+
+ private Object[] interleaveTypes(String[] strs, int[] ints) {
+ assertThat(strs.length).isEqualTo(ints.length);
+
+ Object[] ret = new Object[strs.length * 2];
+
+ // Keep track of where we are in the interleaved array, but iterate the overrides
+ int interleavedIndex = 0;
+ for (int i = 0; i < strs.length; i++) {
+ ret[interleavedIndex] = strs[i];
+ interleavedIndex += 1;
+ ret[interleavedIndex] = ints[i];
+ interleavedIndex += 1;
+ }
+ return ret;
+ }
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index fa96a2f..0b7b2f9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -112,10 +112,10 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.providers.settings.SettingsState.Setting;
-import libcore.util.HexEncoding;
-
import com.google.android.collect.Sets;
+import libcore.util.HexEncoding;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -1144,7 +1144,7 @@
Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
}
- DeviceConfig.enforceReadPermission(getContext(), /*namespace=*/name.split("/")[0]);
+ DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]);
// Get the value.
synchronized (mLock) {
@@ -1317,7 +1317,7 @@
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
}
- DeviceConfig.enforceReadPermission(getContext(),
+ DeviceConfig.enforceReadPermission(
prefix != null ? prefix.split("/")[0] : null);
synchronized (mLock) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index aea2f52..f5c9bcd 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -884,7 +884,7 @@
Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING,
Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
Settings.Secure.SPATIAL_AUDIO_ENABLED,
- Settings.Secure.TIMEOUT_TO_USER_ZERO,
+ Settings.Secure.TIMEOUT_TO_DOCK_USER,
Settings.Secure.UI_NIGHT_MODE_LAST_COMPUTED,
Settings.Secure.UI_NIGHT_MODE_OVERRIDE_OFF,
Settings.Secure.UI_NIGHT_MODE_OVERRIDE_ON);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 90fab08..01c0809 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -720,6 +720,60 @@
<!-- Permission required for CTS test - CtsDeviceLockTestCases -->
<uses-permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE" />
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+
+ <!-- Permission required for CTS test - ApplicationExemptionsTests -->
+ <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/res/values-en-rCA/strings.xml b/packages/Shell/res/values-en-rCA/strings.xml
index 5462813..65ab725 100644
--- a/packages/Shell/res/values-en-rCA/strings.xml
+++ b/packages/Shell/res/values-en-rCA/strings.xml
@@ -28,7 +28,7 @@
<string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Select to share your bug report without a screenshot or wait for the screenshot to finish"</string>
<string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
<string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
- <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data that you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps that you trust."</string>
+ <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps you trust."</string>
<string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Don\'t show again"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"Bug reports"</string>
<string name="bugreport_unreadable_text" msgid="586517851044535486">"Bug report file could not be read"</string>
diff --git a/packages/SimAppDialog/res/values-en-rCA/strings.xml b/packages/SimAppDialog/res/values-en-rCA/strings.xml
index 1ddbaf9..7983c04 100644
--- a/packages/SimAppDialog/res/values-en-rCA/strings.xml
+++ b/packages/SimAppDialog/res/values-en-rCA/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="8898068901680117589">"Sim app dialogue"</string>
+ <string name="app_name" msgid="8898068901680117589">"Sim App Dialog"</string>
<string name="install_carrier_app_title" msgid="334729104862562585">"Activate mobile service"</string>
<string name="install_carrier_app_description" msgid="4014303558674923797">"To get your new SIM working properly, you\'ll need to install the <xliff:g id="ID_1">%1$s</xliff:g> app"</string>
<string name="install_carrier_app_description_default" msgid="7356830245205847840">"To get your new SIM working properly, you\'ll need to install the carrier app"</string>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index 4c237b9..b0708356 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
<string name="add_alarm_text" msgid="3545497316166999225">"Add alarm"</string>
<string name="add_notification_text" msgid="4431129543300614788">"Add notification"</string>
<string name="delete_ringtone_text" msgid="201443984070732499">"Delete"</string>
- <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="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>
</resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d46a93c..7c3948a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -91,6 +91,7 @@
"SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUISharedLib",
+ "SystemUICustomizationLib",
"SystemUI-statsd",
"SettingsLib",
"androidx.core_core-ktx",
@@ -114,8 +115,8 @@
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
+ "androidx.test.ext.junit",
"com.google.android.material_material",
- "kotlin-reflect",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
"iconloader_base",
@@ -191,6 +192,7 @@
"SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUISharedLib",
+ "SystemUICustomizationLib",
"SystemUI-statsd",
"SettingsLib",
"androidx.viewpager2_viewpager2",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 07e37d3..11237dc 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -195,6 +195,9 @@
<permission android:name="com.android.systemui.permission.FLAGS"
android:protectionLevel="signature" />
+ <permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+ android:protectionLevel="signature|privileged" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
@@ -971,10 +974,11 @@
<receiver android:name=".media.dialog.MediaOutputDialogReceiver"
android:exported="true">
- <intent-filter>
+ <intent-filter android:priority="1">
<action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" />
<action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" />
<action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" />
+ <action android:name="android.intent.action.SHOW_OUTPUT_SWITCHER" />
</intent-filter>
</receiver>
@@ -986,5 +990,18 @@
<action android:name="com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG" />
</intent-filter>
</receiver>
+
+ <activity android:name=".logcat.LogAccessDialogActivity"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar"
+ android:excludeFromRecents="true"
+ android:exported="false">
+ </activity>
+
+ <provider
+ android:authorities="com.android.systemui.keyguard.quickaffordance"
+ android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+ android:exported="true"
+ android:permission="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+ />
</application>
</manifest>
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
index 1d808ba..74e6d85 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
@@ -59,11 +59,11 @@
!hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java))
) {
context.report(
- ISSUE,
- method,
- context.getLocation(node),
- "This method should be annotated with `@WorkerThread` because " +
- "it calls ${method.name}",
+ issue = ISSUE,
+ location = context.getLocation(node),
+ message =
+ "This method should be annotated with `@WorkerThread` because " +
+ "it calls ${method.name}",
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
index 1129929..344d0a3 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -52,10 +52,9 @@
val evaluator = context.evaluator
if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`Context.${method.name}()` should be replaced with " +
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`Context.${method.name}()` should be replaced with " +
"`BroadcastSender.${method.name}()`"
)
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
index bab76ab..14099eb 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
@@ -38,10 +38,9 @@
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace with injected `@Main Executor`."
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Replace with injected `@Main Executor`."
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
index b622900..aa4b2f7 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -44,11 +44,11 @@
method.containingClass?.qualifiedName == CLASS_CONTEXT
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Use `@Inject` to get system-level service handles instead of " +
- "`Context.getSystemService()`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Use `@Inject` to get system-level service handles instead of " +
+ "`Context.getSystemService()`"
)
} else if (
evaluator.isStatic(method) &&
@@ -56,10 +56,10 @@
method.containingClass?.qualifiedName == "android.accounts.AccountManager"
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 4ba3afc..5840e8f 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -38,10 +38,10 @@
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Register `BroadcastReceiver` using `BroadcastDispatcher` instead " +
+ "of `Context`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
index 7be21a5..b15a41b 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
@@ -46,10 +46,10 @@
method.containingClass?.qualifiedName == "android.app.ActivityManager"
) {
context.report(
- ISSUE_SLOW_USER_ID_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
+ issue = ISSUE_SLOW_USER_ID_QUERY,
+ location = context.getNameLocation(node),
+ message =
+ "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
)
}
if (
@@ -58,10 +58,9 @@
method.containingClass?.qualifiedName == "android.os.UserManager"
) {
context.report(
- ISSUE_SLOW_USER_INFO_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
+ issue = ISSUE_SLOW_USER_INFO_QUERY,
+ location = context.getNameLocation(node),
+ message = "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4b9aa13..bf02589 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -44,10 +44,9 @@
val evaluator = context.evaluator
if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
context.report(
- ISSUE,
- referenced,
- context.getNameLocation(reference),
- "Replace software bitmap with `Config.HARDWARE`"
+ issue = ISSUE,
+ location = context.getNameLocation(reference),
+ message = "Replace software bitmap with `Config.HARDWARE`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
index 1db0725..22f15bd 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -66,10 +66,9 @@
val subclassName = className.substring(CLASS_SETTINGS.length + 1)
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`@Inject` a ${subclassName}Settings instead"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`@Inject` a ${subclassName}Settings instead"
)
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index c35ac61..426211e 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -126,6 +126,32 @@
}
@Test
+ fun testSuppressUnbindService() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+
+ @SuppressLint("BindServiceOnMainThread")
+ public class TestClass {
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testWorkerMethod() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 376acb5..30b68f7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -129,6 +129,34 @@
}
@Test
+ fun testSuppressSendBroadcastInActivity() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.app.Activity;
+ import android.os.UserHandle;
+
+ public class TestClass {
+ @SuppressWarnings("BroadcastSentViaContext")
+ public void send(Activity activity) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+ }
+
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BroadcastSentViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testSendBroadcastInBroadcastSender() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index 301c338..ed3d14a 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -61,6 +61,32 @@
}
@Test
+ fun testSuppressGetMainThreadHandler() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Handler;
+
+ @SuppressWarnings("NonInjectedMainThread")
+ public class TestClass {
+ public void test(Context context) {
+ Handler mainThreadHandler = context.getMainThreadHandler();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetMainLooper() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 0a74bfc..846129a 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -91,6 +91,32 @@
}
@Test
+ fun testSuppressGetServiceWithClass() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressLint("NonInjectedService")
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService(UserManager.class);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetAccountManager() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 9ed7aa0..0ac8f8e 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -63,6 +63,34 @@
}
@Test
+ fun testSuppressRegisterReceiver() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+
+ @SuppressWarnings("RegisterReceiverViaContext")
+ public class TestClass {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter) {
+ context.registerReceiver(receiver, filter, 0);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testRegisterReceiverAsUser() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 54cac7b..34a4249 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -76,7 +76,7 @@
import android.os.UserManager;
public class TestClass {
- public void slewlyGetUserInfo(UserManager userManager) {
+ public void slowlyGetUserInfo(UserManager userManager) {
userManager.getUserInfo();
}
}
@@ -101,6 +101,34 @@
}
@Test
+ fun testSuppressGetUserInfo() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressWarnings("SlowUserInfoQuery")
+ public void slowlyGetUserInfo(UserManager userManager) {
+ userManager.getUserInfo();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(
+ SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
+ SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testUserTrackerGetUserId() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index c632636..34becc6 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -63,6 +63,31 @@
}
@Test
+ fun testSuppressSoftwareBitmap() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ @SuppressWarnings("SoftwareBitmap")
+ public class TestClass {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testHardwareBitmap() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index b83ed70..efe4c90 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -28,7 +28,7 @@
override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
@Test
- fun testGetServiceWithString() {
+ fun testSuppressGetServiceWithString() {
lint()
.files(
TestFiles.java(
@@ -204,5 +204,34 @@
)
}
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+
+ import android.provider.Settings;
+ import android.provider.Settings.Global;
+ import android.provider.Settings.Secure;
+
+ public class TestClass {
+ @SuppressWarnings("StaticSettingsProvider")
+ public void getSystemServiceWithoutDagger(Context context) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(StaticSettingsProviderDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
private val stubs = androidStubs
}
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
index eada40e..278a89f 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -34,6 +34,11 @@
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
+ <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+ android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
<provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
android:authorities="com.android.systemui.test.keyguard.clock.disabled"
android:enabled="false"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
deleted file mode 100644
index 4d94bab..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ /dev/null
@@ -1,392 +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.user.ui.compose
-
-import android.graphics.drawable.Drawable
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.drawable.toBitmap
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.SysUiOutlinedButton
-import com.android.systemui.compose.SysUiTextButton
-import com.android.systemui.compose.features.R
-import com.android.systemui.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import java.lang.Integer.min
-import kotlin.math.ceil
-
-@Composable
-fun UserSwitcherScreen(
- viewModel: UserSwitcherViewModel,
- onFinished: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false)
- val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList())
- val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
- val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList())
- val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false)
- val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false)
-
- UserSwitcherScreenStateless(
- isFinishRequested = isFinishRequested,
- users = users,
- maxUserColumns = maxUserColumns,
- menuActions = menuActions,
- isOpenMenuButtonVisible = isOpenMenuButtonVisible,
- isMenuVisible = isMenuVisible,
- onMenuClosed = viewModel::onMenuClosed,
- onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked,
- onCancelButtonClicked = viewModel::onCancelButtonClicked,
- onFinished = {
- onFinished()
- viewModel.onFinished()
- },
- modifier = modifier,
- )
-}
-
-@Composable
-private fun UserSwitcherScreenStateless(
- isFinishRequested: Boolean,
- users: List<UserViewModel>,
- maxUserColumns: Int,
- menuActions: List<UserActionViewModel>,
- isOpenMenuButtonVisible: Boolean,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- onOpenMenuButtonClicked: () -> Unit,
- onCancelButtonClicked: () -> Unit,
- onFinished: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- LaunchedEffect(isFinishRequested) {
- if (isFinishRequested) {
- onFinished()
- }
- }
-
- Box(
- modifier =
- modifier
- .fillMaxSize()
- .padding(
- horizontal = 60.dp,
- vertical = 40.dp,
- ),
- ) {
- UserGrid(
- users = users,
- maxUserColumns = maxUserColumns,
- modifier = Modifier.align(Alignment.Center),
- )
-
- Buttons(
- menuActions = menuActions,
- isOpenMenuButtonVisible = isOpenMenuButtonVisible,
- isMenuVisible = isMenuVisible,
- onMenuClosed = onMenuClosed,
- onOpenMenuButtonClicked = onOpenMenuButtonClicked,
- onCancelButtonClicked = onCancelButtonClicked,
- modifier = Modifier.align(Alignment.BottomEnd),
- )
- }
-}
-
-@Composable
-private fun UserGrid(
- users: List<UserViewModel>,
- maxUserColumns: Int,
- modifier: Modifier = Modifier,
-) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(44.dp),
- modifier = modifier,
- ) {
- val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt()
- (0 until rowCount).forEach { rowIndex ->
- Row(
- horizontalArrangement = Arrangement.spacedBy(64.dp),
- modifier = modifier,
- ) {
- val fromIndex = rowIndex * maxUserColumns
- val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns)
- users.subList(fromIndex, toIndex).forEach { user ->
- UserItem(
- viewModel = user,
- )
- }
- }
- }
- }
-}
-
-@Composable
-private fun UserItem(
- viewModel: UserViewModel,
-) {
- val onClicked = viewModel.onClicked
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- if (onClicked != null) {
- Modifier.clickable { onClicked() }
- } else {
- Modifier
- }
- .alpha(viewModel.alpha),
- ) {
- Box {
- UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp))
-
- UserItemIcon(
- image = viewModel.image,
- isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible,
- modifier = Modifier.align(Alignment.Center).size(222.dp)
- )
- }
-
- // User name
- val text = viewModel.name.load()
- if (text != null) {
- // We use the box to center-align the text vertically as that is not possible with Text
- // alone.
- Box(
- modifier = Modifier.size(width = 222.dp, height = 48.dp),
- ) {
- Text(
- text = text,
- style = MaterialTheme.typography.titleLarge,
- color = colorResource(com.android.internal.R.color.system_neutral1_50),
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
- }
-}
-
-@Composable
-private fun UserItemBackground(
- modifier: Modifier = Modifier,
-) {
- Image(
- painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground),
- contentDescription = null,
- modifier = modifier.clip(CircleShape),
- )
-}
-
-@Composable
-private fun UserItemIcon(
- image: Drawable,
- isSelectionMarkerVisible: Boolean,
- modifier: Modifier = Modifier,
-) {
- Image(
- bitmap = image.toBitmap().asImageBitmap(),
- contentDescription = null,
- modifier =
- if (isSelectionMarkerVisible) {
- // Draws a ring
- modifier.border(
- width = 8.dp,
- color = LocalAndroidColorScheme.current.colorAccentPrimary,
- shape = CircleShape,
- )
- } else {
- modifier
- }
- .padding(16.dp)
- .clip(CircleShape)
- )
-}
-
-@Composable
-private fun Buttons(
- menuActions: List<UserActionViewModel>,
- isOpenMenuButtonVisible: Boolean,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- onOpenMenuButtonClicked: () -> Unit,
- onCancelButtonClicked: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- Row(
- modifier = modifier,
- ) {
- // Cancel button.
- SysUiTextButton(
- onClick = onCancelButtonClicked,
- ) {
- Text(stringResource(R.string.cancel))
- }
-
- // "Open menu" button.
- if (isOpenMenuButtonVisible) {
- Spacer(modifier = Modifier.width(8.dp))
- // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it
- // and the menu itself in a Box.
- Box {
- SysUiOutlinedButton(
- onClick = onOpenMenuButtonClicked,
- ) {
- Text(stringResource(R.string.add))
- }
- Menu(
- viewModel = menuActions,
- isMenuVisible = isMenuVisible,
- onMenuClosed = onMenuClosed,
- )
- }
- }
- }
-}
-
-@Composable
-private fun Menu(
- viewModel: List<UserActionViewModel>,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4
- DropdownMenu(
- expanded = isMenuVisible,
- onDismissRequest = onMenuClosed,
- modifier =
- modifier.background(
- color = MaterialTheme.colorScheme.inverseOnSurface,
- ),
- ) {
- viewModel.forEachIndexed { index, action ->
- MenuItem(
- viewModel = action,
- onClicked = { action.onClicked() },
- topPadding =
- if (index == 0) {
- 16.dp
- } else {
- 0.dp
- },
- bottomPadding =
- if (index == viewModel.size - 1) {
- 16.dp
- } else {
- 0.dp
- },
- modifier = Modifier.sizeIn(maxWidth = maxItemWidth),
- )
- }
- }
-}
-
-@Composable
-private fun MenuItem(
- viewModel: UserActionViewModel,
- onClicked: () -> Unit,
- topPadding: Dp,
- bottomPadding: Dp,
- modifier: Modifier = Modifier,
-) {
- val context = LocalContext.current
- val density = LocalDensity.current
-
- val icon =
- remember(viewModel.iconResourceId) {
- val drawable =
- checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
- val size = with(density) { 20.dp.toPx() }.toInt()
- drawable
- .toBitmap(
- width = size,
- height = size,
- )
- .asImageBitmap()
- }
-
- DropdownMenuItem(
- text = {
- Text(
- text = stringResource(viewModel.textResourceId),
- style = MaterialTheme.typography.bodyMedium,
- )
- },
- onClick = onClicked,
- leadingIcon = {
- Spacer(modifier = Modifier.width(10.dp))
- Image(
- bitmap = icon,
- contentDescription = null,
- )
- },
- modifier =
- modifier
- .heightIn(
- min = 56.dp,
- )
- .padding(
- start = 18.dp,
- end = 65.dp,
- top = topPadding,
- bottom = bottomPadding,
- ),
- )
-}
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
new file mode 100644
index 0000000..dc450bb
--- /dev/null
+++ b/packages/SystemUI/customization/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUICustomizationLib",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/**/*.aidl",
+ ],
+ static_libs: [
+ "PluginCoreLib",
+ "SystemUIAnimationLib",
+ "SystemUIPluginLib",
+ "SystemUIUnfoldLib",
+ "androidx.dynamicanimation_dynamicanimation",
+ "androidx.concurrent_concurrent-futures",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.lifecycle_lifecycle-viewmodel-ktx",
+ "androidx.recyclerview_recyclerview",
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
+ "dagger2",
+ "jsr330",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ min_sdk_version: "current",
+ plugins: ["dagger2-compiler"],
+ kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml b/packages/SystemUI/customization/AndroidManifest.xml
similarity index 61%
copy from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
copy to packages/SystemUI/customization/AndroidManifest.xml
index 7723d05..3277bff 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
+++ b/packages/SystemUI/customization/AndroidManifest.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!--
+ Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,14 +14,8 @@
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.frameworks.servicestests.install_uses_sdk">
+ package="com.android.systemui.customization">
- <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
- <!-- This will fail to install, because minExtensionVersion is not met -->
- <extension-sdk android:sdkVersion="30" android:minExtensionVersion="5" />
- </uses-sdk>
-
- <application>
- </application>
</manifest>
diff --git a/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
similarity index 100%
rename from packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
rename to packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/customization/res/layout/clock_default_large.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_large.xml
rename to packages/SystemUI/customization/res/layout/clock_default_large.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/customization/res/layout/clock_default_small.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_small.xml
rename to packages/SystemUI/customization/res/layout/clock_default_small.xml
diff --git a/packages/SystemUI/customization/res/values/attrs.xml b/packages/SystemUI/customization/res/values/attrs.xml
new file mode 100644
index 0000000..f9d66ee
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/attrs.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+ the documentation output. To suppress comment lines from the documentation
+ output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+ <declare-styleable name="AnimatableClockView">
+ <attr name="dozeWeight" format="integer" />
+ <attr name="lockScreenWeight" format="integer" />
+ <attr name="chargeAnimationDelay" format="integer" />
+ </declare-styleable>
+</resources>
diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
similarity index 100%
rename from packages/SystemUI/shared/res/values/dimens.xml
rename to packages/SystemUI/customization/res/values/dimens.xml
diff --git a/packages/SystemUI/shared/res/values/donottranslate.xml b/packages/SystemUI/customization/res/values/donottranslate.xml
similarity index 100%
rename from packages/SystemUI/shared/res/values/donottranslate.xml
rename to packages/SystemUI/customization/res/values/donottranslate.xml
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 236aa66..22944b8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,9 +33,9 @@
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.customization.R
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
@@ -613,7 +613,7 @@
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
// Constants for the animation
- private val MOVE_INTERPOLATOR = Interpolators.STANDARD
+ private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
// Calculate the positions of all of the digits...
// Offset each digit by, say, 0.1
@@ -637,7 +637,10 @@
// How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
// (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
// before moving).
- private const val MOVE_DIGIT_STEP = 0.1f
+ //
+ // The current specs dictate that each digit should have a 33ms gap between them. The
+ // overall time is 1s right now.
+ private const val MOVE_DIGIT_STEP = 0.033f
// Total available transition time for each digit, taking into account the step. If step is
// 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
similarity index 83%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 48821e8..59b4848 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -20,6 +20,7 @@
import android.os.Handler
import android.provider.Settings
import android.util.Log
+import androidx.annotation.OpenForTesting
import com.android.internal.annotations.Keep
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
@@ -27,8 +28,8 @@
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
-import com.google.gson.Gson
+import com.android.systemui.plugins.PluginManager
+import org.json.JSONObject
private val TAG = ClockRegistry::class.simpleName
private const val DEBUG = true
@@ -41,13 +42,13 @@
val isEnabled: Boolean,
userHandle: Int,
defaultClockProvider: ClockProvider,
+ val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
) {
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
fun onClockChanged()
}
- private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver = object : ContentObserver(handler) {
@@ -70,15 +71,18 @@
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
- gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
+ if (json == null || json.isEmpty()) {
+ return fallbackClockId
+ }
+ ClockSetting.deserialize(json).clockId
} catch (ex: Exception) {
Log.e(TAG, "Failed to parse clock setting", ex)
- DEFAULT_CLOCK_ID
+ fallbackClockId
}
}
set(value) {
try {
- val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+ val json = ClockSetting.serialize(ClockSetting(value, System.currentTimeMillis()))
Settings.Secure.putString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
@@ -154,7 +158,8 @@
}
}
- fun getClocks(): List<ClockMetadata> {
+ @OpenForTesting
+ open fun getClocks(): List<ClockMetadata> {
if (!isEnabled) {
return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
}
@@ -198,8 +203,27 @@
)
@Keep
- private data class ClockSetting(
+ data class ClockSetting(
val clockId: ClockId,
val _applied_timestamp: Long?
- )
+ ) {
+ companion object {
+ private val KEY_CLOCK_ID = "clockId"
+ private val KEY_TIMESTAMP = "_applied_timestamp"
+
+ fun serialize(setting: ClockSetting): String {
+ return JSONObject()
+ .put(KEY_CLOCK_ID, setting.clockId)
+ .put(KEY_TIMESTAMP, setting._applied_timestamp)
+ .toString()
+ }
+
+ fun deserialize(jsonStr: String): ClockSetting {
+ val json = JSONObject(jsonStr)
+ return ClockSetting(
+ json.getString(KEY_CLOCK_ID),
+ if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
similarity index 85%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index ca780c8..8698844 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -20,15 +20,16 @@
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
+import android.view.View
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
+import com.android.systemui.customization.R
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
@@ -80,7 +81,7 @@
}
override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- largeClock.recomputePadding()
+ largeClock.recomputePadding(null)
animations = DefaultClockAnimations(dozeFraction, foldFraction)
events.onColorPaletteChanged(resources)
events.onTimeZoneChanged(TimeZone.getDefault())
@@ -101,6 +102,7 @@
// MAGENTA is a placeholder, and will be assigned correctly in initialize
private var currentColor = Color.MAGENTA
private var isRegionDark = false
+ protected var targetRegion: Rect? = null
init {
view.setColors(currentColor, currentColor)
@@ -112,8 +114,20 @@
this@DefaultClockFaceController.isRegionDark = isRegionDark
updateColor()
}
+
+ override fun onTargetRegionChanged(targetRegion: Rect?) {
+ this@DefaultClockFaceController.targetRegion = targetRegion
+ recomputePadding(targetRegion)
+ }
+
+ override fun onFontSettingChanged(fontSizePx: Float) {
+ view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+ recomputePadding(targetRegion)
+ }
}
+ open fun recomputePadding(targetRegion: Rect?) {}
+
fun updateColor() {
val color =
if (isRegionDark) {
@@ -128,16 +142,25 @@
currentColor = color
view.setColors(DOZE_COLOR, color)
- view.animateAppearOnLockscreen()
+ if (!animations.dozeState.isActive) {
+ view.animateAppearOnLockscreen()
+ }
}
}
inner class LargeClockFaceController(
view: AnimatableClockView,
) : DefaultClockFaceController(view) {
- fun recomputePadding() {
+ override fun recomputePadding(targetRegion: Rect?) {
+ // We center the view within the targetRegion instead of within the parent
+ // view by computing the difference and adding that to the padding.
+ val parent = view.parent
+ val yDiff =
+ if (targetRegion != null && parent is View && parent.isLaidOut())
+ targetRegion.centerY() - parent.height / 2f
+ else 0f
val lp = view.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin = (-0.5f * view.bottom).toInt()
+ lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
view.setLayoutParams(lp)
}
@@ -155,18 +178,6 @@
override fun onTimeZoneChanged(timeZone: TimeZone) =
clocks.forEach { it.onTimeZoneChanged(timeZone) }
- override fun onFontSettingChanged() {
- smallClock.view.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
- )
- largeClock.view.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
- )
- largeClock.recomputePadding()
- }
-
override fun onColorPaletteChanged(resources: Resources) {
largeClock.updateColor()
smallClock.updateColor()
@@ -188,7 +199,7 @@
dozeFraction: Float,
foldFraction: Float,
) : ClockAnimations {
- private val dozeState = AnimationState(dozeFraction)
+ internal val dozeState = AnimationState(dozeFraction)
private val foldState = AnimationState(foldFraction)
init {
@@ -229,7 +240,7 @@
get() = true
}
- private class AnimationState(
+ class AnimationState(
var fraction: Float,
) {
var isActive: Boolean = fraction > 0.5f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
similarity index 90%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 6627c5d..4c0504b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -17,23 +17,21 @@
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.customization.R
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.shared.R
-import javax.inject.Inject
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_NAME = "Default Clock"
const val DEFAULT_CLOCK_ID = "DEFAULT"
/** Provides the default system clock */
-class DefaultClockProvider @Inject constructor(
+class DefaultClockProvider constructor(
val ctx: Context,
val layoutInflater: LayoutInflater,
- @Main val resources: Resources
+ val resources: Resources
) : ClockProvider {
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 06ea381..a156aab 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -16,7 +16,6 @@
-packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
-packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
-packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
-packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -24,9 +23,9 @@
-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 89f5c2c..66e44b9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -70,10 +70,10 @@
}
/** Optional method for dumping debug information */
- fun dump(pw: PrintWriter) { }
+ fun dump(pw: PrintWriter) {}
/** Optional method for debug logging */
- fun setLogBuffer(logBuffer: LogBuffer) { }
+ fun setLogBuffer(logBuffer: LogBuffer) {}
}
/** Interface for a specific clock face version rendered by the clock */
@@ -88,40 +88,37 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
/** Call every time tick */
- fun onTimeTick() { }
+ fun onTimeTick() {}
/** Call whenever timezone changes */
- fun onTimeZoneChanged(timeZone: TimeZone) { }
+ fun onTimeZoneChanged(timeZone: TimeZone) {}
/** Call whenever the text time format changes (12hr vs 24hr) */
- fun onTimeFormatChanged(is24Hr: Boolean) { }
+ fun onTimeFormatChanged(is24Hr: Boolean) {}
/** Call whenever the locale changes */
- fun onLocaleChanged(locale: Locale) { }
-
- /** Call whenever font settings change */
- fun onFontSettingChanged() { }
+ fun onLocaleChanged(locale: Locale) {}
/** Call whenever the color palette should update */
- fun onColorPaletteChanged(resources: Resources) { }
+ fun onColorPaletteChanged(resources: Resources) {}
}
/** Methods which trigger various clock animations */
interface ClockAnimations {
/** Runs an enter animation (if any) */
- fun enter() { }
+ fun enter() {}
/** Sets how far into AOD the device currently is. */
- fun doze(fraction: Float) { }
+ fun doze(fraction: Float) {}
/** Sets how far into the folding animation the device is. */
- fun fold(fraction: Float) { }
+ fun fold(fraction: Float) {}
/** Runs the battery animation (if any). */
- fun charge() { }
+ fun charge() {}
/** Move the clock, for example, if the notification tray appears in split-shade mode. */
- fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+ fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
/**
* Whether this clock has a custom position update animation. If true, the keyguard will call
@@ -135,11 +132,26 @@
/** Events that have specific data about the related face */
interface ClockFaceEvents {
/** Region Darkness specific to the clock face */
- fun onRegionDarknessChanged(isDark: Boolean) { }
+ fun onRegionDarknessChanged(isDark: Boolean) {}
+
+ /**
+ * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+ * design is allowed to ignore this target size on a case-by-case basis.
+ */
+ fun onFontSettingChanged(fontSizePx: Float) {}
+
+ /**
+ * Target region information for the clock face. For small clock, this will match the bounds of
+ * the parent view mostly, but have a target height based on the height of the default clock.
+ * For large clocks, the parent view is the entire device size, but most clocks will want to
+ * render within the centered targetRect to avoid obstructing other elements. The specified
+ * targetRegion is relative to the parent view.
+ */
+ fun onTargetRegionChanged(targetRegion: Rect?) {}
}
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
- val name: String
+ val name: String,
)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
similarity index 93%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
rename to packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
index c89be86..80c64cd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
@@ -12,12 +12,10 @@
* permissions and limitations under the License.
*/
-package com.android.systemui.shared.plugins;
+package com.android.systemui.plugins;
import android.text.TextUtils;
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.annotations.ProvidesInterface;
public interface PluginManager {
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a3b4b38..6976786 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -25,9 +25,15 @@
-keep class ** extends androidx.preference.PreferenceFragment
-keep class com.android.systemui.tuner.*
+
+# The plugins and animation subpackages both act as shared libraries that might be referenced in
+# dynamically-loaded plugin APKs.
-keep class com.android.systemui.plugins.** {
*;
}
+-keep class !com.android.systemui.animation.R$**,com.android.systemui.animation.** {
+ *;
+}
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*;
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index c297149..b49afee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -37,7 +37,6 @@
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
android:clipChildren="false"
android:visibility="gone" />
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index c8ba237..a948c04 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Enter your PIN"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Enter your pattern"</string>
<string name="keyguard_enter_your_password" msgid="7225626204122735501">"Enter your password"</string>
- <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid card."</string>
+ <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid Card."</string>
<string name="keyguard_charged" msgid="5478247181205188995">"Charged"</string>
<string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging wirelessly"</string>
<string name="keyguard_plugged_in_dock" msgid="2122073051904360987">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
@@ -70,7 +70,7 @@
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code you must now contact your carrier to unlock your device."</string>
<string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}other{Incorrect SIM PIN code, you have # remaining attempts. }}"</string>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your carrier."</string>
- <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code; you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
+ <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code, you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
<string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM PIN operation failed!"</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK operation failed!"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
@@ -83,12 +83,12 @@
<string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
- <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
+ <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognized"</string>
<string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"To use Face Unlock, turn on camera access in Settings"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
- <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
<string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index a4e7a5f..f1aa544 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -27,4 +27,6 @@
<integer name="scaled_password_text_size">26</integer>
<dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 0a55cf7..3861d98 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -124,6 +124,8 @@
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
<dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
<!-- 2 * the margin + size should equal the plus_margin -->
<dimen name="user_switcher_icon_large_margin">16dp</dimen>
diff --git a/packages/SystemUI/res-product/values-en-rCA/strings.xml b/packages/SystemUI/res-product/values-en-rCA/strings.xml
index 04e63f5..131c42a 100644
--- a/packages/SystemUI/res-product/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-product/values-en-rCA/strings.xml
@@ -25,7 +25,7 @@
<string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"The device will soon turn off; press to keep it on."</string>
<string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"No SIM card in tablet."</string>
<string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"No SIM card in phone."</string>
- <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN codes do not match"</string>
+ <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN codes does not match"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, this tablet will be reset, which will delete all its data."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, this phone will be reset, which will delete all its data."</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER">%d</xliff:g> times. This tablet will be reset, which will delete all its data."</string>
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
new file mode 100644
index 0000000..eefe364
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="@color/accessibility_window_magnifier_button_bg" />
+ <size
+ android:width="40dp"
+ android:height="40dp"/>
+ <corners android:radius="2dp"/>
+ <stroke
+ android:color="@color/accessibility_window_magnifier_button_bg_stroke"
+ android:width="1dp" />
+ </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 88d8f78f..569ee76 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -30,7 +30,7 @@
</item>
<item android:id="@android:id/progress"
android:gravity="center_vertical|fill_horizontal">
- <com.android.systemui.util.RoundedCornerProgressDrawable
+ <com.android.systemui.util.BrightnessProgressDrawable
android:drawable="@drawable/brightness_progress_full_drawable"
/>
</item>
diff --git a/core/res/res/drawable/grant_permissions_buttons_bottom.xml b/packages/SystemUI/res/drawable/grant_permissions_buttons_bottom.xml
similarity index 100%
rename from core/res/res/drawable/grant_permissions_buttons_bottom.xml
rename to packages/SystemUI/res/drawable/grant_permissions_buttons_bottom.xml
diff --git a/core/res/res/drawable/grant_permissions_buttons_top.xml b/packages/SystemUI/res/drawable/grant_permissions_buttons_top.xml
similarity index 100%
rename from core/res/res/drawable/grant_permissions_buttons_top.xml
rename to packages/SystemUI/res/drawable/grant_permissions_buttons_top.xml
diff --git a/packages/SystemUI/res/drawable/ic_doc_document.xml b/packages/SystemUI/res/drawable/ic_doc_document.xml
new file mode 100644
index 0000000..df9ddab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_doc_document.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF4883F3"
+ android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-1.99 6H7V7h10.01v2zm0 4H7v-2h10.01v2zm-3 4H7v-2h7.01v2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
new file mode 100644
index 0000000..d25cd65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M0,24l0,-4l24,-0l0,4z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M0,24l0,-24l4,-0l0,24z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
new file mode 100644
index 0000000..bb6df3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M24,24l-4,-0l-0,-24l4,-0z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M24,24l-24,-0l-0,-4l24,-0z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
new file mode 100644
index 0000000..8f25930
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M0,0h4v24h-4z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M0,0h24v4h-24z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
new file mode 100644
index 0000000..291db44
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M24,0l-0,4l-24,0l-0,-4z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M24,0l-0,24l-4,0l-0,-24z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume.xml b/packages/SystemUI/res/drawable/ic_ring_volume.xml
new file mode 100644
index 0000000..343fe5d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:pathData="M11,7V2H13V7ZM17.6,9.85 L16.2,8.4 19.75,4.85 21.15,6.3ZM6.4,9.85 L2.85,6.3 4.25,4.85 7.8,8.4ZM12,12Q14.95,12 17.812,13.188Q20.675,14.375 22.9,16.75Q23.2,17.05 23.2,17.45Q23.2,17.85 22.9,18.15L20.6,20.4Q20.325,20.675 19.963,20.7Q19.6,20.725 19.3,20.5L16.4,18.3Q16.2,18.15 16.1,17.95Q16,17.75 16,17.5V14.65Q15.05,14.35 14.05,14.175Q13.05,14 12,14Q10.95,14 9.95,14.175Q8.95,14.35 8,14.65V17.5Q8,17.75 7.9,17.95Q7.8,18.15 7.6,18.3L4.7,20.5Q4.4,20.725 4.038,20.7Q3.675,20.675 3.4,20.4L1.1,18.15Q0.8,17.85 0.8,17.45Q0.8,17.05 1.1,16.75Q3.3,14.375 6.175,13.188Q9.05,12 12,12ZM6,15.35Q5.275,15.725 4.6,16.212Q3.925,16.7 3.2,17.3L4.2,18.3L6,16.9ZM18,15.4V16.9L19.8,18.3L20.8,17.35Q20.075,16.7 19.4,16.225Q18.725,15.75 18,15.4ZM6,15.35Q6,15.35 6,15.35Q6,15.35 6,15.35ZM18,15.4Q18,15.4 18,15.4Q18,15.4 18,15.4Z"
+ android:fillColor="?android:attr/colorPrimary"/>
+
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume_off.xml b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
new file mode 100644
index 0000000..74f30d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
@@ -0,0 +1,34 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+<path
+ android:pathData="M0.8,4.2l8.1,8.1c-2.2,0.5 -5.2,1.6 -7.8,4.4c-0.4,0.4 -0.4,1 0,1.4l2.3,2.3c0.3,0.3 0.9,0.4 1.3,0.1l2.9,-2.2C7.8,18.1 8,17.8 8,17.5v-2.9c0.9,-0.3 1.7,-0.5 2.7,-0.6l8.5,8.5l1.4,-1.4L2.2,2.8L0.8,4.2z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M11,2h2v5h-2z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M21.2,6.3l-1.4,-1.4l-3.6,3.6l1.4,1.4C17.6,9.8 21,6.3 21.2,6.3z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M22.9,16.7c-2.8,-3 -6.2,-4.1 -8.4,-4.5l7.2,7.2l1.3,-1.3C23.3,17.7 23.3,17.1 22.9,16.7z"
+ android:fillColor="?android:attr/colorPrimary"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_speaker_mute.xml b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
new file mode 100644
index 0000000..4e402cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_on.xml b/packages/SystemUI/res/drawable/ic_speaker_on.xml
new file mode 100644
index 0000000..2a90e05
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_on.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_watch.xml b/packages/SystemUI/res/drawable/ic_watch.xml
new file mode 100644
index 0000000..8ff880c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_watch.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24
+ h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0z
+ M12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
new file mode 100644
index 0000000..8f6b4c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="?android:attr/buttonCornerRadius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index ae2537f..f14be41 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -312,22 +312,15 @@
<LinearLayout
android:id="@+id/see_all_layout"
- android:layout_width="match_parent"
+ style="@style/InternetDialog.Network"
android:layout_height="64dp"
- android:clickable="true"
- android:focusable="true"
- android:background="?android:attr/selectableItemBackground"
- android:gravity="center_vertical|center_horizontal"
- android:orientation="horizontal"
- android:paddingStart="22dp"
- android:paddingEnd="22dp">
+ android:paddingStart="20dp">
<FrameLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:clickable="false"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/arrow_forward"
android:src="@drawable/ic_arrow_forward"
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index d27fa19..8b85940 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -34,30 +34,13 @@
android:paddingTop="@dimen/status_bar_padding_top"
android:layout_alignParentEnd="true"
android:gravity="center_vertical|end" >
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<FrameLayout android:id="@+id/system_icons_container"
android:layout_width="wrap_content"
diff --git a/core/res/res/layout/log_access_user_consent_dialog_permission.xml b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
similarity index 96%
rename from core/res/res/layout/log_access_user_consent_dialog_permission.xml
rename to packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
index 3da14c8..89e36ac 100644
--- a/core/res/res/layout/log_access_user_consent_dialog_permission.xml
+++ b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
@@ -75,7 +75,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/log_access_confirmation_allow"
- style="@style/PermissionGrantButtonTop"
+ style="?permissionGrantButtonTopStyle"
android:textAppearance="@style/PermissionGrantButtonTextAppearance"
android:layout_marginBottom="5dp"
android:layout_centerHorizontal="true"
@@ -89,7 +89,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/log_access_confirmation_deny"
- style="@style/PermissionGrantButtonBottom"
+ style="?permissionGrantButtonBottomStyle"
android:textAppearance="@style/PermissionGrantButtonTextAppearance"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 9b8b611..530db0d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -44,7 +44,7 @@
android:background="@drawable/qs_media_outline_album_bg"
/>
- <com.android.systemui.ripple.MultiRippleView
+ <com.android.systemui.surfaceeffects.ripple.MultiRippleView
android:id="@+id/touch_ripple_view"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_media_session_height_expanded"
@@ -53,6 +53,15 @@
app:layout_constraintTop_toTopOf="@id/album_art"
app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ app:layout_constraintStart_toStartOf="@id/album_art"
+ app:layout_constraintEnd_toEndOf="@id/album_art"
+ app:layout_constraintTop_toTopOf="@id/album_art"
+ app:layout_constraintBottom_toBottomOf="@id/album_art" />
+
<!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
new file mode 100644
index 0000000..d6c9e98
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -0,0 +1,87 @@
+<!--
+ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <ImageView
+ android:layout_width="@dimen/screenrecord_option_icon_size"
+ android:layout_height="@dimen/screenrecord_option_icon_size"
+ android:src="@drawable/ic_mic_26dp"
+ android:tint="?android:attr/textColorSecondary"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="0"
+ android:layout_marginRight="@dimen/screenrecord_option_padding"
+ android:importantForAccessibility="no"/>
+ <Spinner
+ android:id="@+id/screen_recording_options"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:layout_weight="1"
+ android:popupBackground="@drawable/screenrecord_spinner_background"
+ android:dropDownWidth="274dp"
+ android:importantForAccessibility="yes"/>
+ <Switch
+ android:layout_width="wrap_content"
+ android:minWidth="48dp"
+ android:layout_height="48dp"
+ android:layout_weight="0"
+ android:layout_gravity="end"
+ android:id="@+id/screenrecord_audio_switch"
+ style="@style/ScreenRecord.Switch"
+ android:importantForAccessibility="yes"/>
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/show_taps"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="@dimen/screenrecord_option_padding">
+ <ImageView
+ android:layout_width="@dimen/screenrecord_option_icon_size"
+ android:layout_height="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="0"
+ android:src="@drawable/ic_touch"
+ android:tint="?android:attr/textColorSecondary"
+ android:layout_gravity="center_vertical"
+ android:layout_marginRight="@dimen/screenrecord_option_padding"
+ android:importantForAccessibility="no"/>
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:minHeight="48dp"
+ android:layout_weight="1"
+ android:gravity="center_vertical"
+ android:text="@string/screenrecord_taps_label"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:textColor="?android:attr/textColorPrimary"
+ android:contentDescription="@string/screenrecord_taps_label"/>
+ <Switch
+ android:layout_width="wrap_content"
+ android:minWidth="48dp"
+ android:layout_height="48dp"
+ android:layout_weight="0"
+ android:id="@+id/screenrecord_taps_switch"
+ style="@style/ScreenRecord.Switch"
+ android:importantForAccessibility="yes"/>
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
new file mode 100644
index 0000000..ac46cdb
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -0,0 +1,94 @@
+<!--
+ 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.
+ -->
+
+<!-- Scrollview is necessary to fit everything in landscape layout -->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/screen_share_permission_dialog"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ android:paddingTop="@dimen/dialog_top_padding"
+ android:paddingBottom="@dimen/dialog_bottom_padding"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/screenrecord_logo_size"
+ android:layout_height="@dimen/screenrecord_logo_size"
+ android:src="@drawable/ic_screenrecord"
+ android:tint="@color/screenrecord_icon_color"
+ android:importantForAccessibility="no"/>
+ <TextView
+ android:id="@+id/screen_share_dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:layout_marginTop="22dp"
+ android:layout_marginBottom="15dp"/>
+ <Spinner
+ android:id="@+id/screen_share_mode_spinner"
+ android:layout_width="320dp"
+ android:layout_height="72dp"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="24dp" />
+ <ViewStub
+ android:id="@+id/options_stub"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/text_warning"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/screenrecord_description"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:textColorSecondary"
+ android:gravity="start"
+ android:layout_marginBottom="20dp"/>
+
+ <!-- Buttons -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="36dp">
+ <TextView
+ android:id="@+id/button_cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:text="@string/cancel"
+ style="@style/Widget.Dialog.Button.BorderButton" />
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/button_start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:text="@string/screenrecord_start"
+ style="@style/Widget.Dialog.Button" />
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 1ac78d4..8842992 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -44,7 +44,7 @@
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_percent="1.0"
app:layout_constraintWidth_max="wrap"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"
app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
@@ -70,7 +70,7 @@
android:alpha="0"
android:background="@drawable/overlay_border"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end"
app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/>
<androidx.constraintlayout.widget.Barrier
@@ -142,4 +142,41 @@
app:layout_constraintStart_toStartOf="@id/screenshot_preview"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
android:elevation="7dp"/>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/screenshot_message_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginVertical="4dp"
+ android:paddingHorizontal="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:elevation="4dp"
+ android:background="@drawable/action_chip_container_background"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent">
+
+ <ImageView
+ android:id="@+id/screenshot_message_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:paddingEnd="4dp"
+ android:src="@drawable/ic_work_app_badge"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+
+ <TextView
+ android:id="@+id/screenshot_message_content"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
</com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 80e65a3..f7600e6 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -136,31 +136,12 @@
android:gravity="center_vertical|end"
android:clipChildren="false">
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
new file mode 100644
index 0000000..b374074
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/user_switcher_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ android:background="@drawable/status_bar_user_chip_bg"
+ android:visibility="visible" >
+ <ImageView android:id="@+id/current_user_avatar"
+ android:layout_width="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_height="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_margin="4dp"
+ android:scaleType="centerInside" />
+
+ <TextView android:id="@+id/current_user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="8dp"
+ android:textAppearance="@style/TextAppearance.StatusBar.UserChip"
+ />
+</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 8388b67..bafdb11 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,12 +26,12 @@
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"
- >
+ 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"
@@ -49,7 +49,7 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_notifications"
@@ -57,17 +57,17 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.statusbar.LightRevealScrim
- android:id="@+id/light_reveal_scrim"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:id="@+id/light_reveal_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
<include layout="@layout/status_bar_expanded"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
<include layout="@layout/brightness_mirror_container" />
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 0bff47c..0be7328 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -40,19 +40,22 @@
android:id="@+id/top_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
- android:layout_alignParentTop="true"/>
+ android:layout_alignParentTop="true"
+ android:accessibilityTraversalAfter="@id/left_handle"/>
<View
android:id="@+id/right_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
- android:layout_alignParentEnd="true"/>
+ android:layout_alignParentEnd="true"
+ android:accessibilityTraversalAfter="@id/top_handle"/>
<View
android:id="@+id/bottom_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
- android:layout_alignParentBottom="true"/>
+ android:layout_alignParentBottom="true"
+ android:accessibilityTraversalAfter="@id/right_handle"/>
<SurfaceView
android:id="@+id/surface_view"
@@ -62,6 +65,58 @@
</RelativeLayout>
<ImageView
+ android:id="@+id/top_right_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="right|top"
+ android:paddingTop="@dimen/magnifier_drag_handle_padding"
+ android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_top_right"
+ android:accessibilityTraversalAfter="@id/top_left_corner"/>
+
+ <ImageView
+ android:id="@+id/top_left_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="left|top"
+ android:paddingTop="@dimen/magnifier_drag_handle_padding"
+ android:paddingStart="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_top_left"
+ android:accessibilityTraversalAfter="@id/bottom_handle"/>
+
+ <ImageView
+ android:id="@+id/bottom_right_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="right|bottom"
+ android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+ android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_bottom_right"
+ android:accessibilityTraversalAfter="@id/top_right_corner"/>
+
+ <ImageView
+ android:id="@+id/bottom_left_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="left|bottom"
+ android:paddingStart="@dimen/magnifier_drag_handle_padding"
+ android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_bottom_left"
+ android:accessibilityTraversalAfter="@id/bottom_right_corner"/>
+
+ <ImageView
android:id="@+id/drag_handle"
android:layout_width="@dimen/magnification_drag_view_size"
android:layout_height="@dimen/magnification_drag_view_size"
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 887e3e7..f1bc883 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.systemui.ripple.RippleView
+ <com.android.systemui.surfaceeffects.ripple.RippleView
android:id="@+id/wireless_charging_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 2f2780b..afa9818 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Kan nie gesig herken nie"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gebruik eerder vingerafdruk"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth gekoppel."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterypersentasie is onbekend."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Gekoppel aan <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Bestuur gebruikers"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Maak toe"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Gekoppel"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofoon beskikbaar"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera beskikbaar"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofoon en kamera beskikbaar"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Ander toestel"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Wissel oorsig"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Jy sal nie deur geluide en vibrasies gepla word nie, behalwe deur wekkers, herinneringe, geleenthede en bellers wat jy spesifiseer. Jy sal steeds enigiets hoor wat jy kies om te speel, insluitend musiek, video\'s en speletjies."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Wanneer jy ’n program deel, opneem of uitsaai, het <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Gaan voort"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deel of neem ’n program op"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Bestuur"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geskiedenis"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Skakel mobiele data af?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Jy sal nie deur <xliff:g id="CARRIER">%s</xliff:g> toegang tot data of die internet hê nie. Internet sal net deur Wi-Fi beskikbaar wees."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jou diensverskaffer"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Skakel weer oor na <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiele data sal nie outomaties op grond van beskikbaarheid oorskakel nie"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nee, dankie"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, skakel oor"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Instellings kan nie jou antwoord verifieer nie omdat \'n program \'n toestemmingversoek verberg."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Dit kan inligting van <xliff:g id="APP">%1$s</xliff:g> af lees"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Vergrootglasvensterinstellings"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik om toeganklikheidkenmerke oop te maak Pasmaak of vervang knoppie in Instellings.\n\n"<annotation id="link">"Bekyk instellings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Skuif knoppie na kant om dit tydelik te versteek"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ontdoen"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} kortpad is verwyder}other{# kortpaaie is verwyder}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Beweeg na links bo"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Beweeg na regs bo"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Beweeg na links onder"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Beweeg na regs onder"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Beweeg na rand en versteek"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Beweeg weg van rand en wys"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Verwyder"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"wissel"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Toestelkontroles"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Kies program om kontroles by te voeg"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Gekoppel"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tydelik gekoppel"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Swak verbinding"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data sal nie outomaties koppel nie"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding nie"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen ander netwerke beskikbaar nie"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 3ccb686..5c4e766 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"መልክን መለየት አልተቻለም"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"በምትኩ የጣት አሻራን ይጠቀሙ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ብሉቱዝ ተያይዟል።"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"የባትሪ መቶኛ አይታወቅም።"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ከ<xliff:g id="BLUETOOTH">%s</xliff:g> ጋር ተገናኝቷል።"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ተጠቃሚዎችን ያስተዳድሩ"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ተከናውኗል"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ዝጋ"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"ተገናኝቷል"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ማይክሮፎን አለ"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ካሜራ አለ"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ማይክሮፎን እና ካሜራ አለ"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ማይክሮፎን በርቷል"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ማይክሮፎን ጠፍቷል"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ማይክሮፎን ለሁሉም መተግበሪያዎች እና አገልግሎቶች ነቅቷል።"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"የማይክሮፎን መዳረሻ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ተሰናክሏል። የማይክሮፎን መዳረሻን በቅንብሮች > ግላዊነት > ማይክሮፎን ውስጥ ማንቃት ይችላሉ።"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"የማይክሮፎን መዳረሻ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ተሰናክሏል። ይህን በቅንብሮች > ግላዊነት > ማይክሮፎን ውስጥ መቀየር ይችላሉ።"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ካሜራ በርቷል"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ካሜራ ጠፍቷል"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ካሜራ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ነቅቷል።"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"የካሜራ መዳረሻ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ተሰናክሏል።"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"የማይክሮፎን አዝራርን ለመጠቀም በቅንብሮች ውስጥ የማይክሮፎን መዳረሻን ያንቁ።"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ቅንብሮችን ክፈት።"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"ሌላ መሣሪያ"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"አጠቃላይ እይታን ቀያይር"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"እርስዎ ከወሰንዋቸው ማንቂያዎች፣ አስታዋሾች፣ ክስተቶች እና ደዋዮች በስተቀር፣ በድምጾች እና ንዝረቶች አይረበሹም። ሙዚቃ፣ ቪዲዮዎች እና ጨዋታዎች ጨምሮ ለመጫወት የሚመርጡትን ማንኛውም ነገር አሁንም ይሰማሉ።"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ቀጥል"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"መተግበሪያ ያጋሩ ወይም ይቅረጹ"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"የተንቀሳቃሽ ስልክ ውሂብ ይጥፋ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"በ<xliff:g id="CARRIER">%s</xliff:g> በኩል የውሂብ ወይም የበይነመረቡ መዳረሻ አይኖረዎትም። በይነመረብ በWi-Fi በኩል ብቻ ነው የሚገኝ የሚሆነው።"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"የእርስዎ አገልግሎት አቅራቢ"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ወደ <xliff:g id="CARRIER">%s</xliff:g> ተመልሶ ይቀየር?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"የተንቀሳቃሽ ስልክ ውሂብ በተገኝነት መሰረት በራስ ሰር አይቀይርም"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"አይ አመሰግናለሁ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"አዎ፣ ቀይር"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"አንድ መተግበሪያ የፍቃድ ጥያቄ እያገደ ስለሆነ ቅንብሮች ጥያቄዎን ማረጋገጥ አይችሉም።"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን እንዲያሳይ ይፈቀድለት?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ከ<xliff:g id="APP">%1$s</xliff:g> የመጣ መረጃን ማንበብ ይችላል"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"የማጉያ መስኮት ቅንብሮች"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"የተደራሽነት ባህሪያትን ለመክፈት መታ ያድርጉ። ይህንን አዝራር በቅንብሮች ውስጥ ያብጁ ወይም ይተኩ።\n\n"<annotation id="link">"ቅንብሮችን አሳይ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ለጊዜው ለመደበቅ አዝራሩን ወደ ጠርዝ ያንቀሳቅሱ"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ቀልብስ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} አቋራጭ ተወግዷል}one{# አቋራጭ ተወግዷል}other{# አቋራጮች ተወግደዋል}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ወደ ላይኛው ግራ አንቀሳቅስ"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ወደ ላይኛው ቀኝ አንቀሳቅስ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"የግርጌውን ግራ አንቀሳቅስ"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ታችኛውን ቀኝ አንቀሳቅስ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ወደ ጠርዝ አንቀሳቅስ እና ደደብቅ"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ጠርዙን ወደ ውጭ አንቀሳቅስ እና አሳይ"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"አስወግድ"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ቀያይር"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"የመሣሪያ መቆጣጠሪያዎች"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"መቆጣጠሪያዎችን ለማከል መተግበሪያ ይምረጡ"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"የተንቀሳቃሽ ስልክ ውሂብ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ተገናኝቷል"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"በጊዜያዊነት ተገናኝቷል"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ደካማ ግንኙነት"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"የተንቀሳቃሽ ስልክ ውሂብ በራስ-ሰር አይገናኝም"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ግንኙነት የለም"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ሌላ አውታረ መረብ የሉም"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index c90fb25..f9b866d 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"يتعذّر التعرّف على الوجه."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"يمكنك استخدام بصمة إصبعك."</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"نسبة شحن البطارية غير معروفة."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"متصل بـ <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"إدارة المستخدمين"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"تم"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"إغلاق"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"متصل"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"يمكنك الوصول إلى الميكروفون الآن."</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"يمكنك الوصول إلى الكاميرا الآن."</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"يمكنك الوصول إلى الميكروفون والكاميرا الآن."</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"جهاز آخر"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تبديل \"النظرة العامة\""</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"لن يتم إزعاجك بالأصوات والاهتزاز، باستثناء المُنبِّهات والتذكيرات والأحداث والمتصلين الذين تحددهم. وسيظل بإمكانك سماع أي عناصر أخرى تختار تشغيلها، بما في ذلك الموسيقى والفيديوهات والألعاب."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"متابعة"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"مشاركة محتوى تطبيق أو تسجيله"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"هل تريد إيقاف بيانات الجوّال؟"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"لن تتمكّن من استخدام البيانات أو الإنترنت من خلال <xliff:g id="CARRIER">%s</xliff:g>. ولن يتوفر اتصال الإنترنت إلا عبر Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"مشغّل شبكة الجوّال"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"هل تريد التبديل مرة أخرى إلى \"<xliff:g id="CARRIER">%s</xliff:g>\"؟"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"لن يتم تلقائيًا تبديل بيانات الجوّال بناءً على التوفّر."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"لا، شكرًا"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"نعم، أريد التبديل"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"لا يمكن للإعدادات التحقق من ردك لأن هناك تطبيقًا يحجب طلب الإذن."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"هل تريد السماح لتطبيق <xliff:g id="APP_0">%1$s</xliff:g> بعرض شرائح <xliff:g id="APP_2">%2$s</xliff:g>؟"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- يستطيع قراءة المعلومات من <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"إعدادات نافذة مكبّر الشاشة"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"انقر لفتح ميزات تسهيل الاستخدام. يمكنك تخصيص هذا الزر أو استبداله من الإعدادات.\n\n"<annotation id="link">"عرض الإعدادات"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"يمكنك نقل الزر إلى الحافة لإخفائه مؤقتًا."</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"تراجع"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{تمت إزالة اختصار واحد ({label}).}zero{تمت إزالة # اختصار.}two{تمت إزالة اختصارَين.}few{تمت إزالة # اختصارات.}many{تمت إزالة # اختصارًا.}other{تمت إزالة # اختصار.}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"نقل إلى أعلى يمين الشاشة"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"نقل إلى أعلى يسار الشاشة"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"نقل إلى أسفل يمين الشاشة"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"نقل إلى أسفل يسار الشاشة"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"نقله إلى الحافة وإخفاؤه"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"نقله إلى خارج الحافة وإظهاره"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"إزالة"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"إيقاف/تفعيل"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"التحكم بالجهاز"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"اختيار تطبيق لإضافة عناصر التحكّم"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"بيانات الجوّال"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"متصلة بالإنترنت"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"متصلة مؤقتًا"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"الاتصال ضعيف"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"لن يتم تلقائيًا الاتصال ببيانات الجوّال."</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"لا يتوفّر اتصال بالإنترنت"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"لا تتوفّر شبكات أخرى."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index c363eee..a341820 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"মুখাৱয়ব চিনিব নোৱাৰি"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ইয়াৰ সলনি ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযোগ হ’ল।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"বেটাৰীৰ চাৰ্জৰ শতাংশ অজ্ঞাত।"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>ৰ লগত সংযোগ কৰা হ’ল।"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ব্যৱহাৰকাৰী পৰিচালনা কৰক"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন কৰা হ’ল"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"বন্ধ কৰক"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"সংযোগ কৰা হ’ল"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"মাইক্ৰ’ফ’ন উপলব্ধ"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"কেমেৰা উপলব্ধ"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"মাইক্ৰ’ফ’ন আৰু কেমেৰা উপলব্ধ"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"মাইক্ৰ’ফ’ন অন কৰা হ’ল"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"মাইক্ৰ’ফ’ন অফ কৰা হ’ল"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে মাইক্ৰ’ফ’ন সক্ষম কৰা হৈছে।"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে মাইক্ৰ’ফ’নৰ এক্সেছ অক্ষম কৰা হৈছে। আপুনি ছেটিং > গোপনীয়তা > মাইক্ৰ’ফ’নত মাইক্ৰ’ফ’নৰ এক্সেছ সক্ষম কৰিব পাৰে।"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে মাইক্ৰ’ফ’নৰ এক্সেছ অক্ষম কৰা হৈছে। আপুনি এইটো ছেটিং > গোপনীয়তা > মাইক্ৰ’ফ’নত সলনি কৰিব পাৰে।"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"কেমেৰা অন কৰা হ’ল"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"কেমেৰা অফ কৰা হ’ল"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে কেমেৰা সক্ষম কৰা হৈছে।"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে কেমেৰাৰ এক্সেছ অক্ষম কৰা হৈছে।"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"মাইক্ৰ’ফ’নৰ বুটামটো ব্যৱহাৰ কৰিবলৈ, ছেটিঙত মাইক্ৰ’ফ’নৰ এক্সেছ সক্ষম কৰক।"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ছেটিং খোলক।"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"অন্য ডিভাইচ"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"অৱলোকন ট’গল কৰক"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"আপুনি নিৰ্দিষ্ট কৰা এলাৰ্ম, ৰিমাইণ্ডাৰ, ইভেন্ট আৰু কল কৰোঁতাৰ বাহিৰে আন কোনো শব্দৰ পৰা আপুনি অসুবিধা নাপাব। কিন্তু, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি আপুনি প্লে কৰিব খোজা যিকোনো বস্তু তথাপি শুনিব পাৰিব।"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"অব্যাহত ৰাখক"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"এটা এপ্ শ্বেয়াৰ অথবা ৰেকৰ্ড কৰক"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ম’বাইল ডেটা অফ কৰিবনে?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"আপুনি <xliff:g id="CARRIER">%s</xliff:g>ৰ জৰিয়তে ডেটা সংযোগ বা ইণ্টাৰনেট সংযোগ নাপাব। কেৱল ৱাই-ফাইৰ যোগেৰে ইণ্টাৰনেট উপলব্ধ হ\'ব।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপোনাৰ বাহক"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"আকৌ <xliff:g id="CARRIER">%s</xliff:g>লৈ সলনি কৰিবনে?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ম’বাইলৰ ডেটা উপলব্ধতাৰ ওপৰত ভিত্তি কৰি স্বয়ংক্ৰিয়ভাৱে সলনি কৰা নহ’ব"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"নালাগে, ধন্যবাদ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"হয়, সলনি কৰক"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"এটা এপে অনুমতি বিচাৰি কৰা অনুৰোধ এটা ঢাকি ধৰা বাবে ছেটিঙৰ পৰা আপোনাৰ উত্তৰ সত্যাপন কৰিব পৰা নাই।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ক <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাবলৈ অনুমতি দিবনে?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ তথ্য পঢ়িব পাৰে"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"বিবৰ্ধকৰ ৱিণ্ড’ৰ ছেটিং"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"সাধ্য সুবিধাসমূহ খুলিবলৈ টিপক। ছেটিঙত এই বুটামটো কাষ্টমাইজ অথবা সলনি কৰক।\n\n"<annotation id="link">"ছেটিং চাওক"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"বুটামটোক সাময়িকভাৱে লুকুৱাবলৈ ইয়াক একেবাৰে কাষলৈ লৈ যাওক"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"আনডু কৰক"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}one{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}other{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"শীৰ্ষৰ বাওঁফালে নিয়ক"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"শীৰ্ষৰ সোঁফালে নিয়ক"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"তলৰ বাওঁফালে নিয়ক"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"তলৰ সোঁফালে নিয়ক"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"কাষলৈ নিয়ক আৰু লুকুৱাওক"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"কাষৰ বাহিৰলৈ নিয়ক আৰু দেখুৱাওক"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"আঁতৰাওক"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ট’গল কৰক"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইচৰ নিয়ন্ত্ৰণসমূহ"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"নিয়ন্ত্ৰণসমূহ যোগ কৰিবলৈ এপ্ বাছনি কৰক"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ম’বাইল ডেটা"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"সংযোজিত হৈ আছে"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"অস্থায়ীভাৱে সংযোগ কৰা হৈছে"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"বেয়া সংযোগ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ম’বাইল ডেটা স্বয়ংক্ৰিয়ভাৱে সংযুক্ত নহ’ব"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"সংযোগ নাই"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনো নেটৱৰ্ক উপলব্ধ নহয়"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index c5f143b..ed95d80 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Üzü tanımaq olmur"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmaq izi istifadə edin"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth qoşulub."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareyanın faizi naməlumdur."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> üzərindən qoşuldu."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"İstifadəçiləri idarə edin"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Hazır"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Bağlayın"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Qoşulu"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon əlçatandır"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera əlçatandır"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon və kamera əlçatandır"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon aktiv edilib"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon deaktiv edilib"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon bütün tətbiqlər və xidmətlər üçün aktiv edilib."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofon girişi bütün tətbiqlər və xidmətlər üçün deaktiv edilib. Mikrofon girişini Ayarlar > Məxfilik > Mikrofon bölməsində aktiv edə bilərsiniz."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofon girişi bütün tətbiqlər və xidmətlər üçün deaktiv edilib. Bunu Ayarlar > Məxfilik > Mikrofon bölməsində dəyişə bilərsiniz."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera aktivdir"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera deaktivdir"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera bütün tətbiqlər və xidmətlər üçün aktiv edilib."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera girişi bütün tətbiqlər və xidmətlər üçün deaktiv edilib."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofon düyməsini istifadə etmək üçün Ayarlarda mikrofona girişi aktiv edin."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ayarları açın."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Digər cihaz"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"İcmala Keçin"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Seçdiyiniz siqnal, xatırladıcı, tədbir və zənglər istisna olmaqla səslər və vibrasiyalar Sizi narahat etməyəcək. Musiqi, video və oyunlar da daxil olmaqla oxutmaq istədiyiniz hər şeyi eşidəcəksiniz."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Paylaşdığınız, qeydə aldığınız və ya yayımladığınız zaman <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tətbiqi həmin tətbiqdə göstərilən və ya oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davam edin"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Tətbiqi paylaşın və ya qeydə alın"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"İdarə edin"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Tarixçə"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil data söndürülsün?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ilə data və ya internetə daxil ola bilməyəcəksiniz. İnternet yalnız Wi-Fi ilə əlçatan olacaq."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorunuz"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operatoruna keçirilsin?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobil data əlçatımlıq əsasında avtomatik olaraq keçirilməyəcək"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Xeyr, təşəkkürlər"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Bəli, keçirin"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Tətbiq icazə sorğusunu gizlətdiyi üçün Ayarlar cavabınızı doğrulaya bilməz."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> tətbiqinə <xliff:g id="APP_2">%2$s</xliff:g> hissələrini göstərmək üçün icazə verilsin?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> tətbiqindən məlumat oxuya bilər"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Böyüdücü pəncərə ayarları"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Əlçatımlılıq funksiyalarını açmaq üçün toxunun. Ayarlarda bu düyməni fərdiləşdirin və ya dəyişdirin.\n\n"<annotation id="link">"Ayarlara baxın"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düyməni müvəqqəti gizlətmək üçün kənara çəkin"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri qaytarın"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} qısayol silindi}other{# qısayol silindi}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuxarıya sola köçürün"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Yuxarıya sağa köçürün"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Aşağıya sola köçürün"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Aşağıya sağa köçürün"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"İçəri keçirib gizlədin"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Kənara daşıyıb göstərin"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Silin"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"keçirin"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Cihaz kontrolları"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Kontrol əlavə etmək üçün tətbiq seçin"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Qoşulub"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Müvəqqəti qoşulub"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Zəif bağlantı"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil data avtomatik qoşulmayacaq"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yoxdur"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Heç bir başqa şəbəkə əlçatan deyil"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index e61b692..a2cbce79 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je priključen."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procenat napunjenosti baterije nije poznat."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezani ste sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Upravljajte korisnicima"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zatvori"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Povezan"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je dostupan"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je dostupna"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i kamera su dostupni"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Drugi uređaj"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Uključi/isključi pregled"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Neće vas uznemiravati zvukovi i vibracije osim za alarme, podsetnike, događaje i pozivaoce koje navedete. I dalje ćete čuti sve što odaberete da pustite, uključujući muziku, video snimke i igre."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"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. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Delite ili snimite aplikaciju"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite da isključite mobilne podatke?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ili internetu preko mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo preko WiFi veze."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mobilni operater"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Želite da se vratite na mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilni podaci se neće automatski promeniti na osnovu dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, pređi"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Podešavanja ne mogu da verifikuju vaš odgovor jer aplikacija skriva zahtev za dozvolu."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Može da čita podatke iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Podešavanja prozora za uvećanje"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za funkcije pristupačnosti. Prilagodite ili zamenite ovo dugme u Podešavanjima.\n\n"<annotation id="link">"Podešavanja"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomerite dugme do ivice da biste ga privremeno sakrili"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Opozovite"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjeno}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premesti gore levo"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premesti gore desno"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premesti dole levo"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premesti dole desno"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premesti do ivice i sakrij"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Premesti izvan ivice i prikaži"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Uklonite"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"uključite/isključite"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju za dodavanje kontrola"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Veza je loša"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nije uspelo autom. povezivanje preko mob. podataka"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Veza nije uspostavljena"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 827370f..3d05c86 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Твар не распазнаны"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скарыстайце адбітак пальца"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-сувязь."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Працэнт зараду акумулятара невядомы."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Падлучаны да <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Кіраваць карыстальнікамі"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Гатова"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрыць"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Падлучана"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Мікрафон можна выкарыстоўваць"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камеру можна выкарыстоўваць"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Мікрафон і камеру можна выкарыстоўваць"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Іншая прылада"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Уключыць/выключыць агляд"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Вас не будуць турбаваць гукі і вібрацыя, за выключэннем будзільнікаў, напамінаў, падзей і выбраных вамі абанентаў. Вы будзеце чуць усё, што ўключыце, у тым ліку музыку, відэа і гульні."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Калі пачынаецца абагульванне, запіс ці трансляцыя змесціва праграмы, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> атрымлівае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далей"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Абагульванне або запіс праграмы"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Кіраваць"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Гісторыя"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Выключыць мабільную перадачу даных?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"У вас не будзе доступу да даных ці інтэрнэту праз аператара <xliff:g id="CARRIER">%s</xliff:g>. Інтэрнэт будзе даступны толькі праз Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш аператар"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Зноў пераключыцца на аператара \"<xliff:g id="CARRIER">%s</xliff:g>\"?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мабільны інтэрнэт не будзе аўтаматычна пераключацца ў залежнасці ад даступнасці"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, дзякуй"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Так, пераключыцца"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Праграма хавае запыт на дазвол, таму ваш адказ немагчыма спраўдзіць у Наладах."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Дазволіць праграме <xliff:g id="APP_0">%1$s</xliff:g> паказваць зрэзы праграмы <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Можа счытваць інфармацыю з праграмы <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Налады акна лупы"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Націсніце, каб адкрыць спецыяльныя магчымасці. Рэгулюйце ці замяняйце кнопку ў Наладах.\n\n"<annotation id="link">"Прагляд налад"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Каб часова схаваць кнопку, перамясціце яе на край"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Адрабіць"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Выдалены {label} ярлык}one{Выдалены # ярлык}few{Выдалена # ярлыкі}many{Выдалена # ярлыкоў}other{Выдалена # ярлыка}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перамясціць лявей і вышэй"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перамясціць правей і вышэй"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перамясціць лявей і ніжэй"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перамясціць правей і ніжэй"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перамясціць на край і схаваць"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Перамясціць за край і паказаць"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Выдаліць"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"уключыць/выключыць"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Элементы кіравання прыладай"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Выберыце праграму для дадавання элементаў кіравання"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мабільная перадача даных"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Падключана"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Падключана часова"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Нестабільнае падключэнне"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мабільная перадача даных не ўключаецца аўтаматычна"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма падключэння"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Больш няма даступных сетак"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 880445b..7253b498 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Лицето не е разпознато"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Използвайте отпечатък"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е включен."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентът на батерията е неизвестен."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Има връзка с <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Управление на потребителите"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затваряне"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Установена е връзка"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофонът е налице"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камерата е налице"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофонът и камерата са налице"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофонът е включен"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофонът е изключен"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Достъпът до микрофона е активиран за всички приложения и услуги."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Достъпът до микрофона е деактивиран за всички приложения и услуги. Можете да го активирате от „Настройки > Поверителност > Микрофон“."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Достъпът до микрофона е деактивиран за всички приложения и услуги. Можете да промените това от „Настройки > Поверителност > Микрофон“."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камерата е включена"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камерата е изключена"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Достъпът до камерата е активиран за всички приложения и услуги."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Достъпът до камерата е деактивиран за всички приложения и услуги."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Активирайте достъпа до микрофона, за да използвате съответния бутон."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Отваряне на настройките."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Друго устройство"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Превключване на общия преглед"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Няма да бъдете обезпокоявани от звуци и вибрирания освен от будилници, напомняния, събития и обаждания от посочени от вас контакти. Пак ще чувате всичко, което изберете да се пусне, включително музика, видеоклипове и игри."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се изключат ли мобилните данни?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Няма да можете да използвате данни или интернет чрез <xliff:g id="CARRIER">%s</xliff:g>. Ще имате достъп до интернет само през Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"оператора си"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Искате ли да се върнете към <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мрежата за мобилни данни няма да се превключва автоматично въз основа на наличността"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, благодаря"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, превключване"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"От Настройки не може да се получи потвърждение за отговора ви, защото заявката за разрешение се прикрива от приложение."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Искате ли да разрешите на <xliff:g id="APP_0">%1$s</xliff:g> да показва части от <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чете информация от <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Настройки за инструмента за увеличаване на прозорци"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Докоснете, за да отворите функциите за достъпност. Персон./заменете бутона от настройките.\n\n"<annotation id="link">"Преглед на настройките"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете бутона до края, за да го скриете временно"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отмяна"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} пряк път бе премахнат}other{# преки пътища бяха премахнати}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Преместване горе вляво"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Преместване горе вдясно"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Преместване долу вляво"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Преместване долу вдясно"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Преместване в края и скриване"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Преместване в края и показване"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Премахване"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"превключване"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Контроли за устройството"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Изберете приложение, за да добавите контроли"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни данни"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Свързано"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Установена е временна връзка"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слаба връзка"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Връзката за мобилни данни няма да е автоматична"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма връзка"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Няма други налични мрежи"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index c74fc69..915d0eb 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ফেস শনাক্ত করা যায়নি"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"পরিবর্তে ফিঙ্গারপ্রিন্ট ব্যবহার করুন"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযুক্ত হয়েছে৷"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ব্যাটারি কত শতাংশ আছে তা জানা যায়নি।"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>এ সংযুক্ত হয়ে আছে।"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ব্যবহারকারীদের ম্যানেজ করুন"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন হয়েছে"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"বন্ধ করুন"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"সংযুক্ত হয়েছে"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"মাইক্রোফোন উপলভ্য"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ক্যামেরা উপলভ্য"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"মাইক্রোফোন ও ক্যামেরা উপলভ্য"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"মাইক্রোফোন চালু করা আছে"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"মাইক্রোফোন বন্ধ করা আছে"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"সব অ্যাপ ও পরিষেবার জন্য মাইক্রোফোনের অ্যাক্সেস চালু করা হয়েছে।"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"সব অ্যাপ ও পরিষেবার জন্য মাইক্রোফোনের অ্যাক্সেস বন্ধ করা হয়েছে। \'সেটিংস > গোপনীয়তা > মাইক্রোফোন\' বিকল্প থেকে আপনি মাইক্রোফোনের অ্যাক্সেস চালু করতে পারবেন।"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"সব অ্যাপ ও পরিষেবার জন্য মাইক্রোফোনের অ্যাক্সেস বন্ধ করা হয়েছে। \'সেটিংস > গোপনীয়তা > মাইক্রোফোন\' বিকল্প থেকে আপনি এটি পরিবর্তন করতে পারবেন।"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ক্যামেরা চালু করা হয়েছে"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ক্যামেরা বন্ধ করা হয়েছে"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"সব অ্যাপ ও পরিষেবার জন্য ক্যামেরার অ্যাক্সেস চালু করা হয়েছে।"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"সব অ্যাপ ও পরিষেবার জন্য ক্যামেরার অ্যাক্সেস বন্ধ করা হয়েছে।"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"মাইক্রোফোনের বোতাম ব্যবহার করতে, সেটিংস থেকে মাইক্রোফোনের অ্যাক্সেস চালু করুন।"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"সেটিংস খুলুন।"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"অন্য ডিভাইস"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"\'এক নজরে\' বৈশিষ্ট্যটি চালু বা বন্ধ করুন"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"অ্যালার্ম, রিমাইন্ডার, ইভেন্ট, এবং আপনার নির্দিষ্ট করে দেওয়া ব্যক্তিদের কল ছাড়া অন্য কোনও আওয়াজ বা ভাইব্রেশন হবে না। তবে সঙ্গীত, ভিডিও, এবং গেম সহ আপনি যা কিছু চালাবেন তার আওয়াজ শুনতে পাবেন।"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"কোনও অ্যাপ আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"চালিয়ে যান"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"অ্যাপ শেয়ার বা রেকর্ড করা"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"মোবাইল ডেটা বন্ধ করবেন?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"আপনি \'<xliff:g id="CARRIER">%s</xliff:g>\'-এর মাধ্যমে ডেটা অথবা ইন্টারনেট অ্যাক্সেস করতে পারবেন না। শুধুমাত্র ওয়াই-ফাইয়ের মাধ্যমেই ইন্টারনেট অ্যাক্সেস করা যাবে।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপনার পরিষেবা প্রদানকারী"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"আবার <xliff:g id="CARRIER">%s</xliff:g>-এর ডেটায় পরিবর্তন করবেন?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"উপলভ্যতার উপরে ভিত্তি করে অটোমেটিক মোবাইল ডেটায় পরিবর্তন করা হবে না"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"না থাক"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"হ্যাঁ, পাল্টান"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"একটি অ্যাপ কোনও অনুমোদনের অনুরোধকে ঢেকে দিচ্ছে, তাই সেটিংস থেকে আপনার প্রতিক্রিয়া যাচাই করা যাচ্ছে না।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটিকে <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখানোর অনুমতি দেবেন?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর তথ্য অ্যাক্সেস করতে পারবে"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"\'ম্যাগনিফায়ার উইন্ডো\' সেটিংস"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"অ্যাক্সেসিবিলিটি ফিচার খুলতে ট্যাপ করুন। কাস্টমাইজ করুন বা সেটিংসে এই বোতামটি সরিয়ে দিন।\n\n"<annotation id="link">"সেটিংস দেখুন"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"এটি অস্থায়ীভাবে লুকাতে বোতামটি কোণে সরান"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"আগের অবস্থায় ফিরুন"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label}টি শর্টকাট সরানো হয়েছে}one{#টি শর্টকাট সরানো হয়েছে}other{#টি শর্টকাট সরানো হয়েছে}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"উপরে বাঁদিকে সরান"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"উপরে ডানদিকে সরান"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"নিচে বাঁদিকে সরান"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"নিচে ডান দিকে সরান"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"প্রান্তে যান ও আড়াল করুন"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"প্রান্ত থেকে সরান এবং দেখুন"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"সরিয়ে দিন"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"টগল করুন"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইস কন্ট্রোল"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"কন্ট্রোল যোগ করতে অ্যাপ বেছে নিন"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"মোবাইল ডেটা"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"কানেক্ট করা আছে"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"সাময়িকভাবে কানেক্ট করা হয়েছে"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"খারাপ কানেকশন"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"মোবাইল ডেটা নিজে থেকে কানেক্ট হবে না"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"কানেকশন নেই"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনও নেটওয়ার্ক উপলভ্য নেই"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 5356d74..bdb8638 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nije moguće prepoznati lice"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je povezan."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak napunjenosti baterije nije poznat"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezan na <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je dostupan"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je dostupna"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i kamera su dostuni"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je uključen"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je isključen"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon je omogućen za sve aplikacije i usluge."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Pristup mikrofonu je onemogućen za sve aplikacije i usluge. Možete omogućiti pristup mikrofonu u Postavkama > Privatnost > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Pristup mikrofonu je onemogućen za sve aplikacije i usluge. To možete promijeniti u Postavkama > Privatnost > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera je uključena"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera je isključena"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera je omogućena za sve aplikacije i usluge."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Pristup kamere je onemogućen za sve aplikacije i usluge."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Da koristite dugme za mikrofon, omogućite pristup mikrofonu u Postavkama."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otvori postavke."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Drugi uređaj"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Pregled uključivanja/isključivanja"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Neće vas ometati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivalaca koje odredite. I dalje ćete čuti sve što ste odabrali za reprodukciju, uključujući muziku, videozapise i igre."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada aplikaciju dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijelite ili snimite aplikaciju"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historija"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti prijenos podataka na mobilnoj mreži?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ni internetu putem mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem WiFi-ja."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš operater"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vratiti na operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Prijenos podataka na mobilnoj mreži se neće automatski promijeniti na osnovu dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, promijeni"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Postavke ne mogu potvrditi vaš odgovor jer aplikacija zaklanja zahtjev za odobrenje."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Dozvoliti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Može čitati informacije iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Postavke prozora povećala"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite da otvorite funkcije pristupačnosti. Prilagodite ili zamijenite dugme u Postavkama.\n\n"<annotation id="link">"Postavke"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Premjestite dugme do ivice da ga privremeno sakrijete"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Poništavanje"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjenao}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pomjeranje gore lijevo"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pomjeranje gore desno"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pomjeranje dolje lijevo"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pomjeranje dolje desno"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pomjeranje do ivice i sakrivanje"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pomjeranje izvan ivice i prikaz"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Uklanjanje"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktiviranje/deaktiviranje"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju da dodate kontrole"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Prijenos podataka na mobilnoj mreži"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba veza"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Prijenos podataka se neće automatski povezati"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani s mrežom"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Druge mreže nisu dostupne"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b61ee2c..484de00 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"No es reconeix la cara"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilitza l\'empremta digital"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connectat."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Es desconeix el percentatge de bateria."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"S\'ha connectat a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestiona els usuaris"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Fet"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tanca"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Connectat"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micròfon disponible"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Càmera disponible"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Càmera i micròfon disponibles"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Un altre dispositiu"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activa o desactiva Aplicacions recents"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"No t\'interromprà cap so ni cap vibració, tret dels de les alarmes, recordatoris, esdeveniments i trucades de les persones que especifiquis. Continuaràs sentint tot allò que decideixis reproduir, com ara música, vídeos i jocs."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vols desactivar les dades mòbils?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"No tindràs accés a dades ni a Internet mitjançant <xliff:g id="CARRIER">%s</xliff:g>. Internet només estarà disponible per Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"el teu operador de telefonia mòbil"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vols tornar a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Les dades mòbils no canviaran automàticament en funció de la disponibilitat"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gràcies"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, fes el canvi"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Com que hi ha una aplicació que oculta una sol·licitud de permís, no es pot verificar la teva resposta des de la configuració."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vols permetre que <xliff:g id="APP_0">%1$s</xliff:g> mostri porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pot llegir informació de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuració de la finestra de la lupa"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca per obrir funcions d\'accessibilitat. Personalitza o substitueix el botó a Configuració.\n\n"<annotation id="link">"Mostra"</annotation>"."</string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mou el botó a l\'extrem per amagar-lo temporalment"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfés"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{S\'ha suprimit la drecera {label}}other{S\'han suprimit # dreceres}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mou a dalt a l\'esquerra"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mou a dalt a la dreta"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mou a baix a l\'esquerra"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mou a baix a la dreta"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mou dins de les vores i amaga"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mou fora de les vores i mostra"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Suprimeix"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"commuta"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controls de dispositius"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Selecciona l\'aplicació per afegir controls"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dades mòbils"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connectat"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connexió temporal"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexió feble"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Les dades mòbils no es connectaran automàticament"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sense connexió"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hi ha cap altra xarxa disponible"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index ee36468..71bea80 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Obličej nelze rozpoznat"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Použijte otisk prstu"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Rozhraní Bluetooth je připojeno."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procento baterie není známé."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Připojeno k zařízení <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Správa uživatelů"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zavřít"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Připojeno"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je k dispozici"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Fotoaparát je k dispozici"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon a fotoaparát jsou k dispozici"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je zapnutý"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je vypnutý"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Přístup k mikrofonu je aktivován pro všechny aplikace a služby."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Přístup k mikrofonu je deaktivován pro všechny aplikace a služby. Přístup k mikrofonu můžete udělit v Nastavení > Ochrana soukromí > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Přístup k mikrofonu je deaktivován pro všechny aplikace a služby. Můžete to změnit v Nastavení > Ochrana soukromí > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Fotoaparát je zapnutý"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Fotoaparát je vypnutý"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Přístup k fotoaparátu je aktivován pro všechny aplikace a služby."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Přístup k fotoaparátu je deaktivován pro všechny aplikace a služby."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pokud chcete použít tlačítko mikrofonu, v nastavení udělte přístup k mikrofonu."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otevřít nastavení."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Další zařízení"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Přepnout přehled"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Nebudou vás rušit zvuky ani vibrace s výjimkou budíků, upozornění, událostí a volajících, které zadáte. Nadále uslyšíte veškerý obsah, který si sami pustíte (např. hudba, videa nebo hry)."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovat"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Sdílení nebo nahrání aplikace"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vypnout mobilní data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Prostřednictvím <xliff:g id="CARRIER">%s</xliff:g> nebudete moci používat data ani internet. Internet bude dostupný pouze přes Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vašeho operátora"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Přepnout zpět na operátora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilní data se nebudou automaticky přepínat podle dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, díky"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ano, přepnout"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Žádost o oprávnění je blokována jinou aplikací. Nastavení proto vaši odpověď nemůže ověřit."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Povolit aplikaci <xliff:g id="APP_0">%1$s</xliff:g> zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Může číst informace z aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavení okna zvětšení"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Klepnutím otevřete funkce přístupnosti. Tlačítko lze upravit nebo nahradit v Nastavení.\n\n"<annotation id="link">"Nastavení"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Přesunutím tlačítka k okraji ho dočasně skryjete"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Vrátit zpět"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Zkratka {label} byla odstraněna}few{Byly odstraněny # zkratky}many{Bylo odstraněno # zkratky}other{Bylo odstraněno # zkratek}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Přesunout vlevo nahoru"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Přesunout vpravo nahoru"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Přesunout vlevo dolů"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Přesunout vpravo dolů"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Přesunout k okraji a skrýt"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Přesunout okraj ven a zobrazit"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Odstranit"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"přepnout"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Ovládání zařízení"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikaci, pro kterou chcete přidat ovládací prvky"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilní data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Připojeno"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Dočasně připojeno"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Nekvalitní připojení"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilní data se nebudou připojovat automaticky"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Žádné připojení"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Žádné další sítě nejsou k dispozici"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 220014e..0eb3d95 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -161,13 +161,15 @@
<string name="biometric_dialog_last_pattern_attempt_before_wipe_profile" msgid="6045224069529284686">"Hvis du angiver et forkert mønster i næste forsøg, slettes din arbejdsprofil og de tilhørende data."</string>
<string name="biometric_dialog_last_pin_attempt_before_wipe_profile" msgid="545567685899091757">"Hvis du angiver en forkert pinkode i næste forsøg, slettes din arbejdsprofil og de tilhørende data."</string>
<string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"Hvis du angiver en forkert adgangskode i næste forsøg, slettes din arbejdsprofil og de tilhørende data."</string>
- <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sæt fingeren på fingeraftrykslæseren"</string>
+ <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sæt fingeren på fingeraftrykssensoren"</string>
<string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"Ikon for fingeraftryk"</string>
<string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansigtet kan ikke genkendes. Brug fingeraftryk i stedet."</string>
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ansigt kan ikke genkendes"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Brug fingeraftryk i stedet"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tilsluttet."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriniveauet er ukendt."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tilsluttet <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon kan benyttes"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera kan benyttes"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon og kamera kan benyttes"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonen er aktiveret"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonen er deaktiveret"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonen er aktiveret for alle apps og tjenester."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonadgang er deaktiveret for alle apps og tjenester. Du kan aktivere mikrofonadgang under Indstillinger > Privatliv > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonadgang er deaktiveret for alle apps og tjenester. Du kan ændre dette under Indstillinger > Privatliv > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kameraet er aktiveret"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kameraet er deaktiveret"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameraet er aktiveret for alle apps og tjenester."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kameraadgang er deaktiveret for alle apps og tjenester."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Hvis du vil bruge mikrofonknappen, skal du aktivere mikrofonadgang under Indstillinger."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Åbn Indstillinger."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Anden enhed"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Slå Oversigt til/fra"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Du bliver ikke forstyrret af lyde eller vibrationer, undtagen fra alarmer, påmindelser, begivenheder og opkald fra udvalgte personer, du selv angiver. Du kan stadig høre alt, du vælger at afspille, f.eks. musik, videoer og spil."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, optager eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsæt"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller optag en app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du deaktivere mobildata?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du vil ikke have data- eller internetadgang via <xliff:g id="CARRIER">%s</xliff:g>. Der vil kun være adgang til internettet via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"dit mobilselskab"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vil du skifte tilbage til <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobildata skifter ikke automatisk på baggrund af tilgængelighed"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nej tak"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, skift"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Indstillinger kan ikke bekræfte dit svar, da en app dækker for en anmodning om tilladelse."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vil du give <xliff:g id="APP_0">%1$s</xliff:g> tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Den kan læse oplysninger fra <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Indstillinger for lupvindue"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryk for at åbne hjælpefunktioner. Tilpas eller erstat denne knap i Indstillinger.\n\n"<annotation id="link">"Se indstillingerne"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flyt knappen til kanten for at skjule den midlertidigt"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Fortryd"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} genvej blev fjernet}one{# genvej blev fjernet}other{# genveje blev fjernet}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flyt op til venstre"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flyt op til højre"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flyt ned til venstre"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flyt ned til højre"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flyt ud til kanten, og skjul"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flyt ud til kanten, og vis"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Fjern"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"slå til/fra"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Enhedsstyring"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Vælg en app for at tilføje styring"</string>
@@ -938,19 +954,17 @@
<string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Der er problemer med at aflæse dit batteriniveau"</string>
<string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryk for at få flere oplysninger"</string>
<string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm er indstillet"</string>
- <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeraftrykslæser"</string>
+ <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeraftrykssensor"</string>
<string name="accessibility_authenticate_hint" msgid="798914151813205721">"godkende"</string>
<string name="accessibility_enter_hint" msgid="2617864063504824834">"få adgang til enheden"</string>
<string name="keyguard_try_fingerprint" msgid="2825130772993061165">"Brug fingeraftryk for at åbne"</string>
- <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Godkendelse er påkrævet. Sæt fingeren på fingeraftrykslæseren for at godkende."</string>
+ <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Godkendelse er påkrævet. Sæt fingeren på fingeraftrykssensoren for at godkende."</string>
<string name="ongoing_phone_call_content_description" msgid="5332334388483099947">"Igangværende telefonopkald"</string>
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Forbundet"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Midlertidigt forbundet"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dårlig forbindelse"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Ingen automatisk mobildataforbindelse"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Der er ingen forbindelse"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Der er ingen andre tilgængelige netværk"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 0b3af67..2829950 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Gesicht nicht erkannt"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Fingerabdruck verwenden"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Mit Bluetooth verbunden"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akkustand unbekannt."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Mit <xliff:g id="BLUETOOTH">%s</xliff:g> verbunden"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Nutzer verwalten"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Fertig"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Schließen"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Verbunden"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon verfügbar"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera verfügbar"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon und Kamera verfügbar"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon aktiviert"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon deaktiviert"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon ist für alle Apps und Dienste aktiviert."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonzugriff ist für alle Apps und Dienste deaktiviert. Du kannst ihn in den Einstellungen unter „Datenschutz“ > „Mikrofon“ aktivieren."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonzugriff ist für alle Apps und Dienste deaktiviert. Du kannst dies in den Einstellungen unter „Datenschutz“ > „Mikrofon“ ändern."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera eingeschaltet"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera ausgeschaltet"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera ist für alle Apps und Dienste aktiviert."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamerazugriff ist für alle Apps und Dienste deaktiviert."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Wenn du die Mikrofontaste verwenden möchtest, musst du den Mikrofonzugriff in den Einstellungen aktivieren."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Einstellungen öffnen"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Sonstiges Gerät"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Übersicht ein-/ausblenden"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Klingeltöne und die Vibration werden deaktiviert, außer für Weckrufe, Erinnerungen, Termine sowie Anrufe von zuvor von dir festgelegten Personen. Du hörst jedoch weiterhin Sound, wenn du dir Musik anhörst, Videos ansiehst oder Spiele spielst."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobile Daten deaktivieren?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kannst dann nicht mehr über <xliff:g id="CARRIER">%s</xliff:g> auf Daten und das Internet zugreifen. Das Internet ist nur noch über WLAN verfügbar."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"deinen Mobilfunkanbieter"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Zurück zu <xliff:g id="CARRIER">%s</xliff:g> wechseln?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile Daten werden nicht je nach Verfügbarkeit automatisch gewechselt"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nein danke"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, wechseln"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Deine Eingabe wird von \"Einstellungen\" nicht erkannt, weil die Berechtigungsanfrage von einer App verdeckt wird."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> erlauben, Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzuzeigen?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Darf Informationen aus <xliff:g id="APP">%1$s</xliff:g> lesen"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Einstellungen für das Vergrößerungsfenster"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tippe, um die Bedienungshilfen aufzurufen. Du kannst diese Schaltfläche in den Einstellungen anpassen oder ersetzen.\n\n"<annotation id="link">"Zu den Einstellungen"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Durch Ziehen an den Rand wird die Schaltfläche zeitweise ausgeblendet"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Rückgängig machen"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} Verknüpfung entfernt}other{# Verknüpfungen entfernt}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Nach oben links verschieben"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Nach rechts oben verschieben"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Nach unten links verschieben"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Nach unten rechts verschieben"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"An den Rand verschieben und verbergen"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Vom Rand verschieben und anzeigen"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Entfernen"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Wechseln"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Gerätesteuerung"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"App zum Hinzufügen von Steuerelementen auswählen"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile Daten"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Verbunden"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Vorübergehend verbunden"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Schwache Verbindung"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Keine automatische Verbindung über mobile Daten"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Keine Verbindung"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Keine anderen Netzwerke verfügbar"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index d707e3e..51af1f3 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Αδύνατη η αναγν. προσώπου"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Χρησιμ. δακτυλ. αποτύπ."</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Το Bluetooth είναι συνδεδεμένο."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Άγνωστο ποσοστό μπαταρίας."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Συνδέθηκε στο <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Διαθέσιμο μικρόφωνο"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Διαθέσιμη κάμερα"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Διαθέσιμη κάμερα και μικρόφωνο"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Το μικρόφωνο ενεργοποιήθηκε"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Το μικρόφωνο απενεργοποιήθηκε"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Το μικρόφωνο ενεργοποιήθηκε για όλες τις εφαρμογές και τις υπηρεσίες."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Η πρόσβαση στο μικρόφωνο είναι απενεργοποιημένη για όλες τις εφαρμογές και τις υπηρεσίες. Μπορείτε να ενεργοποιήσετε την πρόσβαση στο μικρόφωνο από τις Ρυθμίσεις > Απόρρητο > Μικρόφωνο."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Η πρόσβαση στο μικρόφωνο είναι απενεργοποιημένη για όλες τις εφαρμογές και τις υπηρεσίες. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις > Απόρρητο > Μικρόφωνο."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Η κάμερα ενεργοποιήθηκε"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Η κάμερα απενεργοποιήθηκε"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Η κάμερα ενεργοποιήθηκε για όλες τις εφαρμογές και τις υπηρεσίες."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Η πρόσβαση στην κάμερα είναι απενεργοποιημένη για όλες τις εφαρμογές και τις υπηρεσίες."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Για να χρησιμοποιήσετε το κουμπί μικροφώνου, ενεργοποιήστε την πρόσβαση στο μικρόφωνο από τις Ρυθμίσεις."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Άνοιγμα ρυθμίσεων."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Άλλη συσκευή"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Εναλλαγή επισκόπησης"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Δεν θα ενοχλείστε από ήχους και δονήσεις, παρά μόνο από ξυπνητήρια, υπενθυμίσεις, συμβάντα και καλούντες που έχετε καθορίσει. Θα εξακολουθείτε να ακούτε όλο το περιεχόμενο που επιλέγετε να αναπαραγάγετε, συμπεριλαμβανομένης της μουσικής, των βίντεο και των παιχνιδιών."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση μιας εφαρμογής, η εφαρμογή <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Συνέχεια"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Κοινοποίηση ή εγγραφή εφαρμογής"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Διαχείριση"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ιστορικό"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Απενεργοποίηση δεδομένων κινητής τηλεφωνίας;"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Δεν θα έχετε πρόσβαση σε δεδομένα ή στο διαδίκτυο μέσω της εταιρείας κινητής τηλεφωνίας <xliff:g id="CARRIER">%s</xliff:g>. Θα έχετε πρόσβαση στο διαδίκτυο μόνο μέσω Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"η εταιρεία κινητής τηλεφωνίας"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Επιστροφή σε <xliff:g id="CARRIER">%s</xliff:g>;"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Δεν θα γίνεται αυτόματα εναλλαγή των δεδομένων κινητής τηλεφωνίας βάσει της διαθεσιμότητας"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Όχι, ευχαριστώ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ναι, εναλλαγή"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Επειδή μια εφαρμογή αποκρύπτει ένα αίτημα άδειας, δεν είναι δυνατή η επαλήθευση της απάντησής σας από τις Ρυθμίσεις."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Να επιτρέπεται στο <xliff:g id="APP_0">%1$s</xliff:g> να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>;"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Μπορεί να διαβάζει πληροφορίες από την εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ρυθμίσεις παραθύρου μεγεθυντικού φακού"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Πατήστε για άνοιγμα των λειτουργιών προσβασιμότητας. Προσαρμόστε ή αντικαταστήστε το κουμπί στις Ρυθμίσεις.\n\n"<annotation id="link">"Προβολή ρυθμίσεων"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Μετακινήστε το κουμπί στο άκρο για προσωρινή απόκρυψη"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Αναίρεση"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Η συντόμευση {label} καταργήθηκε}other{Καταργήθηκαν # συντομεύσεις}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Μετακίνηση επάνω αριστερά"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Μετακίνηση επάνω δεξιά"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Μετακίνηση κάτω αριστερά"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Μετακίνηση κάτω δεξιά"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Μετακίν. στο άκρο και απόκρυψη"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Μετακ. εκτός άκρου και εμφάν."</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Κατάργηση"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"εναλλαγή"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Στοιχεία ελέγχου συσκευής"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Επιλογή εφαρμογής για προσθήκη στοιχείων ελέγχου"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Δεδομένα κινητής τηλεφωνίας"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Συνδέθηκε"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Προσωρινή σύνδεση"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Ασθενής σύνδεση"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Χωρίς αυτόματη σύνδεση δεδομένων κινητ. τηλεφωνίας"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Χωρίς σύνδεση"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Δεν υπάρχουν άλλα διαθέσιμα δίκτυα"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 3aaa5f5..c23bfe6 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
@@ -373,6 +386,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -727,14 +745,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data won\'t automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +807,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remove"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -947,10 +958,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index fb56d9f..7c1f8fd 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
@@ -373,6 +386,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -727,14 +745,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your carrier"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data won\'t automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +807,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remove"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -947,10 +958,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings_tv.xml b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
index e97dbe4..a628846 100644
--- a/packages/SystemUI/res/values-en-rCA/strings_tv.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
@@ -23,13 +23,13 @@
<string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
<string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
<string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
- <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string>
+ <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No Notifications"</string>
<string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
<string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
- <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string>
+ <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and Microphone are recording"</string>
<string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
<string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
- <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string>
+ <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and Microphone stopped recording"</string>
<string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string>
<string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 3aaa5f5..c23bfe6 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
@@ -373,6 +386,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -727,14 +745,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data won\'t automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +807,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remove"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -947,10 +958,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 3aaa5f5..c23bfe6 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
@@ -373,6 +386,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -727,14 +745,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data won\'t automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +807,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} shortcut removed}other{# shortcuts removed}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remove"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -947,10 +958,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index ee56838..06b9f45 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognize face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings > Privacy > Microphone."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games."</string>
@@ -373,6 +386,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording, or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording, or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording, or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 6160caf..d6f9a5e 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce el rostro"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella dactilar"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Se desconoce el porcentaje de la batería."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrar usuarios"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Listo"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Cerrar"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micrófono disponible"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cámara disponible"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micrófono y cámara disponibles"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Se activó el micrófono"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Se desactivó el micrófono"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Se habilitó el micrófono para todos los servicios y las apps."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"El acceso al micrófono está inhabilitado para todos los servicios y las apps. Puedes habilitar su acceso en Configuración > Privacidad > Micrófono."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"El acceso al micrófono está inhabilitado para todos los servicios y las apps. Puedes habilitarlo en Configuración > Privacidad > Micrófono."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Se activó la cámara"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Se desactivó la cámara"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Se habilitó la cámara para todos los servicios y las apps."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"El acceso a la cámara está inhabilitado para todos los servicios y las apps."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para habilitar el botón de micrófono, habilita su acceso en Configuración."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir Configuración"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Otro dispositivo"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Ocultar o mostrar Recientes"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas, los recordatorios, los eventos y las llamadas de los emisores que especifiques. Podrás escuchar el contenido que reproduzcas, como música, videos y juegos."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o transmitas una app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Deseas desactivar los datos móviles?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu proveedor"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"¿Volver a cambiar a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Los datos móviles no cambiarán automáticamente en función de la disponibilidad"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gracias"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, cambiar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Como una app está bloqueando una solicitud de permiso, Configuración no puede verificar tu respuesta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información sobre <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración de la ventana de ampliación"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Presiona para abrir las funciones de accesibilidad. Personaliza o cambia botón en Config.\n\n"<annotation id="link">"Ver config"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Se quitó el acceso directo {label}}many{Se quitaron # de accesos directos}other{Se quitaron # accesos directos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover arriba a la derecha"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover abajo a la izquierda"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover abajo a la derecha"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover fuera de borde y ocultar"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover fuera de borde y mostrar"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Quitar"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar o desactivar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controles de dispositivos"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Elige la app para agregar los controles"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conexión establecida"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectado temporalmente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión deficiente"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"No se conectarán automáticamente los datos móviles"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index ed9e7d9..9480e4c 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce la cara"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella digital"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentaje de batería desconocido."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestionar usuarios"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Hecho"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Cerrar"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micrófono disponible"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cámara disponible"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micrófono y cámara disponible"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Micrófono activado"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Micrófono desactivado"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"El micrófono está habilitado para todas las aplicaciones y servicios."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"El acceso al micrófono está inhabilitado para todas las aplicaciones y servicios. Puedes habilitar el acceso al micrófono en Ajustes > Privacidad > Micrófono."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"El acceso al micrófono está inhabilitado para todas las aplicaciones y servicios. Puedes cambiarlo en Ajustes > Privacidad > Micrófono."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Cámara activada"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Cámara desactivada"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"La cámara está habilitada para todas las aplicaciones y servicios."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"El acceso a la cámara está inhabilitado para todas las aplicaciones y servicios."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para usar el botón del micrófono, habilita el acceso al micrófono en Ajustes."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir ajustes"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Otro dispositivo"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Mostrar u ocultar aplicaciones recientes"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas, los recordatorios, los eventos y las llamadas que especifiques. Seguirás escuchando el contenido que quieras reproducir, como música, vídeos y juegos."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o envíes una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una aplicación"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Desactivar datos móviles?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu operador"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"¿Cambiar de nuevo a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Los datos móviles no cambiarán automáticamente en función de la disponibilidad"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gracias"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, cambiar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Una aplicación impide ver una solicitud de permiso, por lo que Ajustes no puede verificar tu respuesta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración de la ventana de la lupa"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir funciones de accesibilidad. Personaliza o sustituye este botón en Ajustes.\n\n"<annotation id="link">"Ver ajustes"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} acceso directo eliminado}many{# accesos directos eliminados}other{# accesos directos eliminados}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover arriba a la derecha"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover abajo a la izquierda"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover abajo a la derecha"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover al borde y ocultar"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover al borde y mostrar"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Quitar"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar/desactivar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Elige una aplicación para añadir controles"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectada temporalmente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión inestable"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Los datos móviles no se conectarán automáticamente"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string>
diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml
index d7a8133..fe4cbed 100644
--- a/packages/SystemUI/res/values-es/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml
@@ -78,8 +78,8 @@
</string-array>
<string-array name="tile_states_location">
<item msgid="3316542218706374405">"No disponible"</item>
- <item msgid="4813655083852587017">"Desactivado"</item>
- <item msgid="6744077414775180687">"Activado"</item>
+ <item msgid="4813655083852587017">"Desactivada"</item>
+ <item msgid="6744077414775180687">"Activada"</item>
</string-array>
<string-array name="tile_states_hotspot">
<item msgid="3145597331197351214">"No disponible"</item>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 417c2c2..c5ac0c7 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nägu ei õnnestu tuvastada"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kasutage sõrmejälge"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth on ühendatud."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Aku laetuse protsent on teadmata."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ühendatud: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kasutajate haldamine"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sule"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Ühendatud"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon on saadaval"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kaamera on saadaval"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon ja kaamera on saadaval"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Muu seade"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Lehe Ülevaade sisse- ja väljalülitamine"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Helid ja värinad ei sega teid. Kuulete siiski enda määratud äratusi, meeldetuletusi, sündmusi ja helistajaid. Samuti kuulete kõike, mille esitamise ise valite, sh muusika, videod ja mängud."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kui jagate, salvestate või kannate rakendust üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jätka"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Rakenduse jagamine või salvestamine"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Kas lülitada mobiilne andmeside välja?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Pärast seda pole teil operaatori <xliff:g id="CARRIER">%s</xliff:g> kaudu juurdepääsu andmesidele ega internetile. Internet on saadaval ainult WiFi kaudu."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"teie operaator"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Kas vahetada tagasi operaatorile <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiilandmeside operaatorit ei vahetata saadavuse alusel automaatselt"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Tänan, ei"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Jah, vaheta"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Seaded ei saa teie vastust kinnitada, sest rakendus varjab loataotlust."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Kas lubada rakendusel <xliff:g id="APP_0">%1$s</xliff:g> näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- See saab lugeda teavet rakendusest <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Luubi akna seaded"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Puudutage juurdepääsufunktsioonide avamiseks. Kohandage nuppu või asendage see seadetes.\n\n"<annotation id="link">"Kuva seaded"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Teisaldage nupp serva, et see ajutiselt peita"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Võta tagasi"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} otsetee on eemaldatud}other{# otseteed on eemaldatud}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Teisalda üles vasakule"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Teisalda üles paremale"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Teisalda alla vasakule"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Teisalda alla paremale"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Teisalda serva ja kuva"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Teisalda servast eemale ja kuva"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Eemalda"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"lülita"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Seadmete juhikud"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Valige juhtelementide lisamiseks rakendus"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilne andmeside"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ühendatud"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ajutiselt ühendatud"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Kehv ühendus"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilset andmesideühendust ei looda automaatselt"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ühendus puudub"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ühtegi muud võrku pole saadaval"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index ee1ba23..2461886 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ezin da ezagutu aurpegia"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Erabili hatz-marka"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Bateriaren ehunekoa ezezaguna da."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> gailura konektatuta."</string>
@@ -248,8 +250,7 @@
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kolore-alderantzikatzea"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kudeatu erabiltzaileak"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Eginda"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Itxi"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Konektatuta"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofonoa erabilgarri dago"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera erabilgarri dago"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofonoa eta kamera erabilgarri daude"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Aktibatu da mikrofonoa"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Desaktibatu da mikrofonoa"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Aplikazio eta zerbitzu guztiek mikrofonoa erabil dezakete."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonoa erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako. Mikrofonoa erabiltzeko baimena gaitzeko, joan Ezarpenak > Pribatutasuna > Mikrofonoa atalera."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonoa erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako. Baimen hori aldatzeko, joan Ezarpenak > Pribatutasuna > Mikrofonoa atalera."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Piztu da kamera"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Itzali da kamera"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Aplikazio eta zerbitzu guztiek kamera erabil dezakete."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofonoaren botoia erabiltzeko, gaitu mikrofonoa erabiltzeko baimena ezarpenetan."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ireki ezarpenak."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Beste gailu bat"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aldatu ikuspegi orokorra"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaera eta abisuen tonuak, eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Egin aurrera"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partekatu edo grabatu aplikazioak"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Datu-konexioa desaktibatu nahi duzu?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> erabilita ezingo dituzu erabili datuak edo Internet. Wifi-sare baten bidez soilik konektatu ahal izango zara Internetera."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Zure operadorea"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operadorera aldatu nahi duzu berriro?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Datu-konexioa ez da automatikoki aldatuko erabilgarritasunaren arabera"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ez, eskerrik asko"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Bai, aldatu nahi dut"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikazio bat baimen-eskaera oztopatzen ari denez, ezarpenek ezin dute egiaztatu erantzuna."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakusteko baimena eman nahi diozu <xliff:g id="APP_0">%1$s</xliff:g> aplikazioari?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioaren informazioa irakur dezake."</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Luparen leihoaren ezarpenak"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erabilerraztasun-eginbideak irekitzeko, sakatu hau. Ezarpenetan pertsonalizatu edo ordez dezakezu botoia.\n\n"<annotation id="link">"Ikusi ezarpenak"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Eraman botoia ertzera aldi baterako ezkutatzeko"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desegin"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} lasterbide kendu da}other{# lasterbide kendu dira}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Eraman goialdera, ezkerretara"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Eraman goialdera, eskuinetara"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Eraman behealdera, ezkerretara"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Eraman behealdera, eskuinetara"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Eraman ertzera eta ezkutatu"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Atera ertzetik eta erakutsi"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Kendu"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aldatu"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Gailuak kontrolatzeko widgetak"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Aukeratu aplikazio bat kontrolatzeko aukerak gehitzeko"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Datu-konexioa"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> (<xliff:g id="NETWORKMODE">%2$s</xliff:g>)"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Konektatuta"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Aldi baterako konektatuta"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Konexio ahula"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Ez da automatikoki aktibatuko datu-konexioa"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Konexiorik gabe"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ez dago beste sare erabilgarririk"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 62d73ae..458b480 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"چهره شناسایی نشد"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"از اثر انگشت استفاده کنید"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوتوث متصل است."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"درصد شارژ باتری مشخص نیست."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"به <xliff:g id="BLUETOOTH">%s</xliff:g> متصل شد."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"مدیریت کاربران"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"تمام"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"بستن"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"متصل"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"میکروفون دردسترس است"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"دوربین دردسترس است"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"میکروفون و دوربین دردسترساند"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"میکروفون روشن شد"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"میکروفون خاموش شد"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"میکروفون برای همه برنامهها و سرویسها فعال است."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"دسترسی به میکروفون برای همه برنامهها و سرویسها غیرفعال است. میتوانید دسترسی به میکروفون را در «تنظیمات > حریم خصوصی > میکروفون» فعال کنید."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"دسترسی به میکروفون برای همه برنامهها و سرویسها غیرفعال است. میتوانید آن را در «تنظیمات > حریم خصوصی > میکروفون» تغییر دهید."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"دوربین روشن شد"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"دوربین خاموش شد"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"دوربین برای همه برنامهها و سرویسها فعال است."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"دسترسی به دوربین برای همه برنامهها و سرویسها غیرفعال است."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"برای استفاده از دکمه میکروفون، دسترسی به میکروفون را در «تنظیمات» فعال کنید."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"باز کردن تنظیمات."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"دستگاه دیگر"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تغییر وضعیت نمای کلی"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"بهجز هشدارها، یادآوریها، رویدادها و تماسگیرندگانی که خودتان مشخص میکنید، هیچ صدا و لرزشی نخواهید داشت. همچنان صدای مواردی را که پخش میکنید میشنوید (ازجمله صدای موسیقی، ویدیو و بازی)."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"وقتی درحال همرسانی، ضبط، یا پخش محتوای برنامهای هستید، <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> به همه محتوایی که در آن برنامه نمایان است یا پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ادامه"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"همرسانی یا ضبط برنامه"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"سابقه"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"داده تلفن همراه خاموش شود؟"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"نمیتوانید ازطریق <xliff:g id="CARRIER">%s</xliff:g> به داده یا اینترنت دسترسی داشته باشید. اینترنت فقط ازطریق Wi-Fi در دسترس خواهد بود."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"شرکت مخابراتی شما"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"میخواهید به <xliff:g id="CARRIER">%s</xliff:g> برگردید؟"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"وضعیت داده تلفن همراه بهطور خودکار براساس دردسترس بودن تغییر نخواهد کرد"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"نه متشکرم"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"بله، عوض شود"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"چون برنامهای درحال ایجاد تداخل در درخواست مجوز است، «تنظیمات» نمیتواند پاسخ شما را تأیید کند."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"به <xliff:g id="APP_0">%1$s</xliff:g> اجازه داده شود تکههای <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد؟"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- میتواند اطلاعات <xliff:g id="APP">%1$s</xliff:g> را بخواند"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"تنظیمات پنجره ذرهبین"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"برای باز کردن ویژگیهای دسترسپذیری ضربه بزنید. در تنظیمات این دکمه را سفارشی یا جایگزین کنید\n\n"<annotation id="link">"تنظیمات"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"برای پنهان کردن موقتی دکمه، آن را به لبه ببرید"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"واگرد"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} میانبر برداشته شد}one{# میانبر برداشته شد}other{# میانبر برداشته شد}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"انتقال به بالا سمت راست"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"انتقال به بالا سمت چپ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"انتقال به پایین سمت راست"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"انتقال به پایین سمت چپ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"انتقال به لبه و پنهان کردن"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"انتقال به خارج از لبه و نمایش"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"برداشتن"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"روشن/ خاموش کردن"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"کنترلهای دستگاه"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"انتخاب برنامه برای افزودن کنترلها"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"داده تلفن همراه"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"متصل است"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"موقتاً متصل است"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"اتصال ضعیف"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"داده تلفن همراه بهطور خودکار متصل نخواهد شد"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"اتصال برقرار نیست"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"شبکه دیگری وجود ندارد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index bfb450f..1da012a 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Kasvoja ei voi tunnistaa"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Käytä sormenjälkeä"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth yhdistetty."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akun varaustaso ei tiedossa."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Yhteys: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Ylläpidä käyttäjiä"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sulje"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Yhdistetty"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofoni käytettävissä"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera käytettävissä"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofoni ja kamera käytettävissä"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofoni laitettu päälle"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofoni laitettu pois päältä"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonin käyttö on sallittu kaikille sovelluksille ja palveluille."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Pääsy mikrofoniin on estetty kaikilta sovelluksilta ja palveluilta. Jos haluat sallia pääsyn mikrofoniin, valitse Asetukset > Yksityisyys > Mikrofoni."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Pääsy mikrofoniin on estetty kaikilta sovelluksilta ja palveluilta. Jos haluat muuttaa tätä, valitse Asetukset > Yksityisyys > Mikrofoni."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera laitettu päälle"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera laitettu pois päältä"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameran käyttö on sallittu kaikille sovelluksille ja palveluille."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Pääsy kameraan on estetty kaikilta sovelluksilta ja palveluilta."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Jos haluat käyttää mikrofonipainiketta, salli pääsy mikrofoniin asetuksista."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Avaa asetukset."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Muu laite"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Näytä/piilota viimeisimmät"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Äänet ja värinät eivät häiritse sinua, paitsi jos ne ovat hälytyksiä, muistutuksia, tapahtumia tai määrittämiäsi soittajia. Kuulet edelleen kaiken valitsemasi sisällön, kuten musiikin, videot ja pelit."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kun jaat, tallennat tai striimaat sovellusta, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jatka"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Jaa sovellus tai tallenna sen sisältöä"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Laitetaanko mobiilidata pois päältä?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ei enää tarjoa pääsyä dataan eikä internetyhteyttä, joka on saatavilla vain Wi-Fin kautta."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operaattorisi"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Palauta käyttöön <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiilidata ei vaihdu automaattisesti saatavuuden perusteella"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ei kiitos"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Kyllä, vaihda"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Sovellus peittää käyttöoikeuspyynnön, joten Asetukset ei voi vahvistaa valintaasi."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Saako <xliff:g id="APP_0">%1$s</xliff:g> näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Se voi lukea tietoja sovelluksesta <xliff:g id="APP">%1$s</xliff:g>."</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ikkunan suurennuksen asetukset"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Avaa esteettömyysominaisuudet napauttamalla. Yksilöi tai vaihda painike asetuksista.\n\n"<annotation id="link">"Avaa asetukset"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Piilota painike tilapäisesti siirtämällä se reunaan"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Kumoa"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} pikanäppäin poistettu}other{# pikanäppäintä poistettu}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Siirrä vasempaan yläreunaan"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Siirrä oikeaan yläreunaan"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Siirrä vasempaan alareunaan"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Siirrä oikeaan alareunaan"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Siirrä reunaan ja piilota"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Siirrä pois reunasta ja näytä"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Poista"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"vaihda"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Laitehallinta"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Valitse sovellus lisätäksesi säätimiä"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilidata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Yhdistetty"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Väliaikaisesti yhdistetty"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Heikko yhteys"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilidata ei yhdisty automaattisesti"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ei yhteyttä"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ei muita verkkoja käytettävissä"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index b2d925c..cc8bdb8 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utiliser l\'empreinte digitale"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la pile inconnu."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gérer les utilisateurs"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Terminé"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fermer"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Connecté"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone disponible"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Appareil photo disponible"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone et appareil photo disponibles"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone activé"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone désactivé"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Le microphone est activé pour toutes les applications et tous les services."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"L\'accès au microphone est désactivé pour toutes les applications et tous les services. Vous pouvez l\'activer dans Paramètres > Confidentialité > Microphone."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"L\'accès au microphone est désactivé pour toutes les applications et tous les services. Vous pouvez modifier cette option dans Paramètres > Confidentialité > Microphone."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Appareil photo activé"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Appareil photo désactivé"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"L\'appareil photo est activé pour toutes les applications et tous les services."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"L\'accès à l\'appareil photo est désactivé pour toutes les applications et tous les services."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pour utiliser le bouton du microphone, activez l\'accès au microphone dans les paramètres."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ouvrir les paramètres."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Autre appareil"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Basculer l\'aperçu"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangé par les sons et les vibrations, sauf pour les alarmes, les rappels, les événements et les appelants que vous sélectionnez. Vous entendrez tout ce que vous choisissez d\'écouter, y compris la musique, les vidéos et les jeux."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou diffusez une application, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est affiché ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une application"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
@@ -608,8 +630,8 @@
<string name="accessibility_status_bar_headset" msgid="2699275863720926104">"Écouteurs connectés"</string>
<string name="data_saver" msgid="3484013368530820763">"Économiseur de données"</string>
<string name="accessibility_data_saver_on" msgid="5394743820189757731">"La fonction Économiseur de données est activée"</string>
- <string name="switch_bar_on" msgid="1770868129120096114">"Activé"</string>
- <string name="switch_bar_off" msgid="5669805115416379556">"Désactivé"</string>
+ <string name="switch_bar_on" msgid="1770868129120096114">"Activée"</string>
+ <string name="switch_bar_off" msgid="5669805115416379556">"Désactivée"</string>
<string name="tile_unavailable" msgid="3095879009136616920">"Non disponible"</string>
<string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"En savoir plus"</string>
<string name="nav_bar" msgid="4642708685386136807">"Barre de navigation"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données cellulaires?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données ni à Internet avec <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre fournisseur de services"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Rebasculer vers <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Il n\'y aura pas de basculement automatique entre les données mobiles selon la disponibilité"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non merci"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oui, basculer"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Une application obscurcit une demande d\'autorisation, alors Paramètres ne peut pas vérifier votre réponse."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Il peut lire de l\'information de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Paramètres de la fenêtre de loupe"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Touchez pour ouvrir fonction. d\'access. Personnalisez ou remplacez bouton dans Param.\n\n"<annotation id="link">"Afficher param."</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacez le bouton vers le bord pour le masquer temporairement"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} raccourci retiré}one{# raccourci retiré}many{# de raccourcis retirés}other{# raccourcis retirés}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer dans coin sup. droit"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer dans coin inf. gauche"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer dans coin inf. droit"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Éloigner du bord et masquer"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Retirer"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"basculer"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'application pour laquelle ajouter des commandes"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Données cellulaires"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connexion active"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connectée temporairement"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexion faible"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Aucune connexion auto. des données cellulaires"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau n\'est accessible"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 12a88c6..1508b9c 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilisez empreinte digit."</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la batterie inconnu."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gérer les utilisateurs"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"OK"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fermer"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Connecté"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micro accessible"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Caméra accessible"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micro et caméra accessibles"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Micro activé"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Micro désactivé"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Le micro est activé pour tous les services et applis."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"L\'accès au micro est désactivé pour tous les services et applis. Vous pouvez activer l\'accès au micro dans Paramètres > Confidentialité > Micro."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"L\'accès au micro est désactivé pour tous les services et applis. Vous pouvez modifier cette option dans Paramètres > Confidentialité > Micro."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Appareil photo activé"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Appareil photo désactivé"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"L\'appareil photo est activé pour tous les services et applis."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"L\'accès à l\'appareil photo est désactivé pour tous les services et applis."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pour utiliser le bouton du micro, activez l\'accès au micro dans les paramètres."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ouvrir les paramètres."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Autre appareil"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activer/Désactiver l\'aperçu"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez dérangé par aucun son ni aucune vibration, hormis ceux des alarmes, des rappels, des événements et des appels des contacts de votre choix. Le son continuera de fonctionner notamment pour la musique, les vidéos et les jeux."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou castez une appli, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une appli"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données mobiles ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données mobiles ni à Internet via <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre opérateur"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Rebasculer sur <xliff:g id="CARRIER">%s</xliff:g> ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Il n\'y aura pas de basculement automatique entre les données mobile selon la disponibilité"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non, merci"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oui, revenir"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"L\'application Paramètres ne peut pas valider votre réponse, car une application masque la demande d\'autorisation."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Accès aux informations de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Paramètres de la fenêtre d\'agrandissement"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Appuyez pour ouvrir fonctionnalités d\'accessibilité. Personnalisez ou remplacez bouton dans paramètres.\n\n"<annotation id="link">"Voir paramètres"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacer le bouton vers le bord pour le masquer temporairement"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} raccourci supprimé}one{# raccourci supprimé}many{# raccourcis supprimés}other{# raccourcis supprimés}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer en haut à droite"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer en bas à gauche"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer en bas à droite"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Rapprocher du bord et masquer"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Supprimer"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activer/désactiver"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'appli pour laquelle ajouter des commandes"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Données mobiles"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connecté"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connectée temporairement"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexion médiocre"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Pas de connexion automatique des données mobiles"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau disponible"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index d18d596..3807a87 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Non se recoñeceu a cara"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa a impresión dixital"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Descoñécese a porcentaxe da batería."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrar usuarios"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Feito"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Pechar"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"O micrófono está dispoñible"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"A cámara está dispoñible"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"O micrófono e a cámara están dispoñibles"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Activouse o micrófono"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Desactivouse o micrófono"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O micrófono está activado para todas as aplicacións e servizos."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acceso ao micrófono está desactivado para todas as aplicacións e servizos. Podes activar o acceso ao micrófono en Configuración > Privacidade > Micrófono."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acceso ao micrófono está desactivado para todas as aplicacións e servizos. Podes cambialo en Configuración > Privacidade > Micrófono."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Activouse a cámara"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Desactivouse a cámara"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A cámara está activada para todas as aplicacións e servizos."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acceso á cámara está desactivado para todas as aplicacións e servizos."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para usar o botón do micrófono, activa o acceso ao micrófono en Configuración."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir configuración."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activar/desactivar Visión xeral"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Non te molestará ningún son nin vibración, agás os procedentes de alarmas, recordatorios, eventos e os emisores de chamada especificados. Seguirás escoitando todo aquilo que decidas reproducir, mesmo a música, os vídeos e os xogos."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cando compartes, gravas ou emites unha aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir ou gravar unha aplicación"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 034c161..d992d6a 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ચહેરો ઓળખાતો નથી"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"તો ફિંગરપ્રિન્ટ વાપરો"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"બ્લૂટૂથ કનેક્ટ થયું."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"બૅટરીની ટકાવારી અજાણ છે."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> થી કનેક્ટ થયાં."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"વપરાશકર્તાઓને મેનેજ કરો"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"થઈ ગયું"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"બંધ કરો"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"કનેક્ટ થયેલું"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"માઇક્રોફોનનો ઍક્સેસ ઉપલબ્ધ છે"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"કૅમેરાનો ઍક્સેસ ઉપલબ્ધ છે"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"માઇક્રોફોન અને કૅમેરાનો ઍક્સેસ ઉપલબ્ધ છે"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"માઇક્રોફોન ચાલુ કર્યું"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"માઇક્રોફોન બંધ કર્યું"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"બધી ઍપ અને સેવાઓ માટે માઇક્રોફોન ચાલુ કર્યું."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"બધી ઍપ અને સેવાઓ માટે માઇક્રોફોનનો ઍક્સેસ બંધ કર્યો છે. તમે સેટિંગ > પ્રાઇવસી > માઇક્રોફોનમાં જઈને માઇક્રોફોનનો ઍક્સેસ ચાલુ કરી શકો છો."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"બધી ઍપ અને સેવાઓ માટે માઇક્રોફોનનો ઍક્સેસ બંધ કર્યો છે. તમે સેટિંગ > પ્રાઇવસી > માઇક્રોફોનમાં જઈને આને બદલી શકો છો."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"કૅમેરા ચાલુ કર્યો"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"કૅમેરા બંધ કર્યો"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"બધી ઍપ અને સેવાઓ માટે કૅમેરા ચાલુ કર્યો."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"બધી ઍપ અને સેવાઓ માટે કૅમેરાનો ઍક્સેસ બંધ કર્યો છે."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"માઇક્રોફોન બટનનો ઉપયોગ કરવા માટે, સેટિંગમાં માઇક્રોફોનનો ઍક્સેસ ચાલુ કરો."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"સેટિંગ ખોલો."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"અન્ય ડિવાઇસ"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ઝલકને ટૉગલ કરો"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"અલાર્મ, રિમાઇન્ડર, ઇવેન્ટ અને તમે ઉલ્લેખ કરો તે કૉલર સિવાય તમને ધ્વનિ કે વાઇબ્રેશન દ્વારા ખલેલ પહોંચાડવામાં આવશે નહીં. સંગીત, વીડિઓ અને રમતો સહિત તમે જે કંઈપણ ચલાવવાનું પસંદ કરશો તે સંભળાતું રહેશે."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી કોઈપણ વસ્તુનો ઍક્સેસ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ધરાવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ચાલુ રાખો"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"કોઈ ઍપ શેર કરો અથવા રેકોર્ડ કરો"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ઇતિહાસ"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"મોબાઇલ ડેટા બંધ કરીએ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"તમને <xliff:g id="CARRIER">%s</xliff:g> મારફતે ડેટા અથવા ઇન્ટરનેટનો ઍક્સેસ મળશે નહીં. ઇન્ટરનેટ માત્ર વાઇ-ફાઇ દ્વારા ઉપલબ્ધ થશે."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"તમારા કૅરિઅર"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> પર પાછા સ્વિચ કરીએ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"મોબાઇલ ડેટાને ઉપલબ્ધતાના આધારે ઑટોમૅટિક રીતે સ્વિચ કરવામાં આવશે નહીં"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ના, આભાર"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"હા, સ્વિચ કરો"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"કોઈ ઍપ પરવાનગી વિનંતીને અસ્પષ્ટ કરતી હોવાને કારણે, સેટિંગ તમારા પ્રતિસાદને ચકાસી શકતું નથી."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ને <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવાની મંજૂરી આપીએ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- મારાથી <xliff:g id="APP">%1$s</xliff:g>ની માહિતી વાંચી શકાતી નથી"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"મેગ્નિફાયર વિન્ડોના સેટિંગ"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ઍક્સેસિબિલિટી સુવિધાઓ ખોલવા માટે ટૅપ કરો. સેટિંગમાં આ બટનને કસ્ટમાઇઝ કરો અથવા બદલો.\n\n"<annotation id="link">"સેટિંગ જુઓ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"તેને હંગામી રૂપે ખસેડવા માટે બટનને કિનારી પર ખસેડો"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"છેલ્લો ફેરફાર રદ કરો"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} શૉર્ટકટ કાઢી નાખ્યો}one{# શૉર્ટકટ કાઢી નાખ્યો}other{# શૉર્ટકટ કાઢી નાખ્યો}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ઉપર ડાબે ખસેડો"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ઉપર જમણે ખસેડો"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"નીચે ડાબે ખસેડો"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"નીચે જમણે ખસેડો"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"કિનારી પર ખસેડો અને છુપાવો"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"કિનારીથી ખસેડો અને બતાવો"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"કાઢી નાખો"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ટૉગલ કરો"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ડિવાઇસનાં નિયંત્રણો"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"નિયંત્રણો ઉમેરવા માટે ઍપ પસંદ કરો"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"મોબાઇલ ડેટા"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"કનેક્ટ કરેલું"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"હંગામી રીતે કનેક્ટ કર્યું"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"નબળું કનેક્શન"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"મોબાઇલ ડેટા ઑટોમૅટિક રીતે કનેક્ટ થશે નહીં"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"કોઈ કનેક્શન નથી"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"બીજાં કોઈ નેટવર્ક ઉપલબ્ધ નથી"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index dc94a8d..e8458dd 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरे की पहचान नहीं हुई"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"फ़िंगरप्रिंट इस्तेमाल करें"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट किया गया."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"इस बारे में जानकारी नहीं है कि अभी बैटरी कितने प्रतिशत चार्ज है."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> से कनेक्ट किया गया."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"उपयोगकर्ताओं को मैनेज करें"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"हो गया"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"रद्द करें"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"कनेक्ट है"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"माइक्रोफ़ोन का ऐक्सेस उपलब्ध है"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"कैमरे का ऐक्सेस उपलब्ध है"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"माइक्रोफ़ोन और कैमरे का ऐक्सेस उपलब्ध है"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"माइक्रोफ़ोन का ऐक्सेस चालू किया गया"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"माइक्रोफ़ोन का ऐक्सेस बंद किया गया"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"माइक्रोफ़ोन का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए चालू किया गया."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"माइक्रोफ़ोन का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए बंद किया गया. इसे चालू करने के लिए, सेटिंग > निजता > माइक्रोफ़ोन पर जाएं."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"माइक्रोफ़ोन का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए बंद किया गया. इसे चालू करने के लिए, सेटिंग > निजता > माइक्रोफ़ोन पर जाएं."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"कैमरे का ऐक्सेस चालू किया गया"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"कैमरे का ऐक्सेस बंद किया गया"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"कैमरे का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए चालू किया गया."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"सभी ऐप्लिकेशन और सेवाओं के लिए कैमरे का ऐक्सेस बंद किया गया."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"माइक्रोफ़ोन बटन का इस्तेमाल करने के लिए, सेटिंग में जाकर माइक्रोफ़ोन का ऐक्सेस चालू करें."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"सेटिंग खोलें."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"अन्य डिवाइस"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"खास जानकारी टॉगल करें"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"आपको अलार्म, रिमाइंडर, इवेंट और चुनिंदा कॉल करने वालों के अलावा किसी और तरह से (आवाज़ करके और थरथरा कर ) परेशान नहीं किया जाएगा. आप फिर भी संगीत, वीडियो और गेम सहित अपना चुना हुआ सब कुछ सुन सकते हैं."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"मैनेज करें"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करना चाहते हैं?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"आप <xliff:g id="CARRIER">%s</xliff:g> से डेटा या इंटरनेट का इस्तेमाल नहीं कर पाएंगे. इंटरनेट सिर्फ़ वाई-फ़ाई से चलेगा."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"आपको मोबाइल और इंटरनेट सेवा देने वाली कंपनी"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"क्या आपको मोबाइल डेटा, <xliff:g id="CARRIER">%s</xliff:g> पर वापस से स्विच करना है?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"उपलब्ध होने पर, मोबाइल डेटा अपने-आप स्विच नहीं होगा"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"स्विच न करें"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"स्विच करें"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ऐप की वजह से मंज़ूरी के अनुरोध को समझने में दिक्कत हो रही है, इसलिए सेटिंग से आपके जवाब की पुष्टि नहीं हो पा रही है."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> को <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- यह <xliff:g id="APP">%1$s</xliff:g> से सूचना पढ़ सकता है"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ज़ूम करने की सुविधा वाली विंडो से जुड़ी सेटिंग"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सुलभता सुविधाएं खोलने के लिए टैप करें. सेटिंग में, इस बटन को बदलें या अपने हिसाब से सेट करें.\n\n"<annotation id="link">"सेटिंग देखें"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटन को कुछ समय छिपाने के लिए, उसे किनारे पर ले जाएं"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहले जैसा करें"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} शॉर्टकट हटाया गया}one{# शॉर्टकट हटाया गया}other{# शॉर्टकट हटाए गए}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सबसे ऊपर बाईं ओर ले जाएं"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"सबसे ऊपर दाईं ओर ले जाएं"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"सबसे नीचे बाईं ओर ले जाएं"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"सबसे नीचे दाईं ओर ले जाएं"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"एज पर ले जाएं और छिपाएं"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"एज से निकालें और दिखाएं"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"हटाएं"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टॉगल करें"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"डिवाइस कंट्रोल"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"कंट्रोल जोड़ने के लिए ऐप्लिकेशन चुनें"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट हो गया"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"इंटरनेट कनेक्शन कुछ समय के लिए है"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"इंटरनेट कनेक्शन खराब है"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा अपने-आप कनेक्ट नहीं होगा"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"इंटरनेट कनेक्शन नहीं है"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"कोई दूसरा नेटवर्क उपलब्ध नहीं है"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index be5253f..d8fd225 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Upotrijebite otisak prsta"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth povezan."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak baterije nije poznat."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Spojen na <xliff:g id="BLUETOOTH">%s</xliff:g> ."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je dostupan"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je dostupna"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i kamera su dostupni"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je uključen"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je isključen"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon je omogućen za sve aplikacije i usluge."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Pristup mikrofonu onemogućen je za sve aplikacije i usluge. Pristup mikrofonu možete omogućiti u odjeljku Postavke > Privatnost > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Pristup mikrofonu onemogućen je za sve aplikacije i usluge. Tu postavku možete promijeniti u odjeljku Postavke > Privatnost > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera je uključena"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera je isključena"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera je omogućena za sve aplikacije i usluge."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Pristup kameri onemogućen je za sve aplikacije i usluge."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Da biste koristili gumb mikrofona, omogućite pristup mikrofonu u postavkama."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otvori postavke"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Ostali uređaji"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Uključivanje/isključivanje pregleda"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Neće vas ometati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivatelja koje navedete. I dalje ćete čuti sve što želite reproducirati, uključujući glazbu, videozapise i igre."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kad dijelite, snimate ili emitirate aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijeljenje ili snimanje pomoću aplikacije"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Povijest"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti mobilne podatke?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup mobilnim podacima ili internetu putem operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem Wi-Fija."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš mobilni operater"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vratiti se na mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilni podaci neće se automatski prebaciti na temelju dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, prebaci"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Budući da aplikacija prekriva zahtjev za dopuštenje, Postavke ne mogu potvrditi vaš odgovor."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Želite li dopustiti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– može čitati informacije aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Postavke prozora povećala"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za otvaranje značajki pristupačnosti. Prilagodite ili zamijenite taj gumb u postavkama.\n\n"<annotation id="link">"Pregledajte postavke"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomaknite gumb do ruba da biste ga privremeno sakrili"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Poništi"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Uklonjen je prečac {label}}one{# prečac je uklonjen}few{# prečaca su uklonjena}other{# prečaca je uklonjeno}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premjesti u gornji lijevi kut"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premjesti u gornji desni kut"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premjesti u donji lijevi kut"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premjesti u donji desni kut"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premjesti na rub i sakrij"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Ukloni s ruba i prikaži"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Ukloni"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"promijeni"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Odabir aplikacije za dodavanje kontrola"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba veza"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna veza neće se automatski uspostaviti"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index cf07438..c1a8dd9 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Az arc nem ismerhető fel"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Használjon ujjlenyomatot"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth csatlakoztatva."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Az akkumulátor töltöttségi szintje ismeretlen."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Csatlakoztatva a következőhöz: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Felhasználók kezelése"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Kész"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Bezárás"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Csatlakoztatva"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"A mikrofon használható"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"A kamera használható"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"A mikrofon és a kamera használható"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon bekapcsolva"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon kikapcsolva"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"A mikrofon az összes alkalmazás és szolgáltatás számára engedélyezve van."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"A mikrofonhoz való hozzáférés az összes alkalmazás és szolgáltatás számára le van tiltva. A mikrofonhoz való hozzáférést a következő menüpontban engedélyezheti: Beállítások > Adatvédelem > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"A mikrofonhoz való hozzáférés az összes alkalmazás és szolgáltatás számára le van tiltva. Ezt a következő menüpontban módosíthatja: Beállítások > Adatvédelem > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera bekapcsolva"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera kikapcsolva"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A kamera az összes alkalmazás és szolgáltatás számára engedélyezve van."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"A kamerához való hozzáférés az összes alkalmazás és szolgáltatás számára le van tiltva."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ha használni szeretné a Mikrofon gombot, engedélyezze a mikrofonhoz való hozzáférést a Beállításokban."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Beállítások megnyitása."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Más eszköz"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Áttekintés be- és kikapcsolása"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Az Ön által meghatározott ébresztéseken, emlékeztetőkön, eseményeken és hívókon kívül nem fogja Önt más hang vagy rezgés megzavarni. Továbbra is lesz hangjuk azoknak a tartalmaknak, amelyeket Ön elindít, például zenék, videók és játékok."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, a(z) <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> az adott appban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Folytatás"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Alkalmazás megosztása és rögzítése"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kezelés"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Előzmények"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Kikapcsolja a mobiladatokat?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nem lesz adat-, illetve internet-hozzáférése a <xliff:g id="CARRIER">%s</xliff:g> szolgáltatón keresztül. Az internethez csak Wi-Fi-n keresztül csatlakozhat."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"saját mobilszolgáltató"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Átvált a következőre: <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Nem kerül sor a mobiladat-forgalom automatikus átváltására a rendelkezésre állás alapján"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Most nem"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Igen, átváltok"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Mivel az egyik alkalmazás eltakarja az engedélykérést, a Beállítások alkalmazás nem tudja ellenőrizni az Ön válaszát."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Engedélyezi a(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazásnak, hogy részleteket mutasson a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Információkat olvashat a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásból"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nagyítóablak beállításai"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Koppintson a kisegítő lehetőségek megnyitásához. A gombot a Beállításokban módosíthatja.\n\n"<annotation id="link">"Beállítások"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"A gombot a szélre áthelyezve ideiglenesen elrejtheti"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Visszavonás"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} gyorsparancs eltávolítva}other{# gyorsparancs eltávolítva}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Áthelyezés fel és balra"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Áthelyezés fel és jobbra"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Áthelyezés le és balra"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Áthelyezés le és jobbra"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Áthelyezés a szélen kívül és elrejtés"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Áthelyezés a szélen kívül és mutatás"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Eltávolítás"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"váltás"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Eszközvezérlők"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Válasszon alkalmazást a vezérlők hozzáadásához"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiladat"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g>/<xliff:g id="STATE">%1$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Csatlakozva"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ideiglenesen csatlakoztatva"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Gyenge kapcsolat"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nincs automatikus mobiladat-kapcsolat"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nincs kapcsolat"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nincs több rendelkezésre álló hálózat"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index bfd051b..691fe64 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Դեմքը չի ճանաչվել"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Օգտագործեք մատնահետք"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-ը միացված է:"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Մարտկոցի լիցքի մակարդակն անհայտ է։"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Միացված է <xliff:g id="BLUETOOTH">%s</xliff:g>-ին:"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Կառավարել օգտատերերին"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Պատրաստ է"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Փակել"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Միացված է"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Խոսափողը հասանելի է"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Տեսախցիկը հասանելի է"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Խոսափողն ու տեսացիկը հասանելի են"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Խոսափողը միացավ"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Խոսափողն անջատվեց"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Խոսափողը բոլոր հավելվածների և ծառայությունների համար միացված է։"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Խոսափողն օգտագործելու թույլտվությունը բոլոր հավելվածների և ծառայությունների համար անջատված է։ Խոսափողի օգտագործումը թույլատրելու համար անցեք Կարգավորումներ > Գաղտնիություն > Խոսափող։"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Խոսափողն օգտագործելու թույլտվությունը բոլոր հավելվածների և ծառայությունների համար անջատված է։ Սա փոխելու համար անցեք Կարգավորումներ > Գաղտնիություն > Խոսափող։"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Տեսախցիկը միացված է"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Տեսախցիկն անջատված է"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Տեսախցիկը բոլոր հավելվածների և ծառայությունների համար միացված է։"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Տեսախցիկն օգտագործելու թույլտվությունը բոլոր հավելվածների և ծառայությունների համար անջատված է։"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Խոսափողի կոճակն օգտագործելու համար կարգավորումներում թույլատրեք խոսափողի օգտագործումը։"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Բացել կարգավորումները։"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Այլ սարք"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Միացնել/անջատել համատեսքը"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Ձայները և թրթռոցները չեն անհանգստացնի ձեզ, բացի ձեր կողմից նշված զարթուցիչները, հիշեցումները, միջոցառումների ծանուցումները և զանգերը։ Դուք կլսեք ձեր ընտրածի նվագարկումը, այդ թվում՝ երաժշտություն, տեսանյութեր և խաղեր:"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Շարունակել"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Հավելվածի էկրանի ցուցադրում կամ տեսագրում"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Անջատե՞լ բջջային ինտերնետը"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> օպերատորի բջջային ինտերնետը հասանելի չի լինի: Համացանցից կկարողանաք օգտվել միայն Wi-Fi-ի միջոցով:"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Ձեր"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Անցնե՞լ <xliff:g id="CARRIER">%s</xliff:g> ցանցին"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Սարքն ավտոմատ չի անցնի բջջային ինտերնետին"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ոչ, շնորհակալություն"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Այո"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Քանի որ ներածումն արգելափակված է ինչ-որ հավելվածի կողմից, Կարգավորումները չեն կարող հաստատել ձեր պատասխանը:"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Թույլատրե՞լ <xliff:g id="APP_0">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Կարող է կարդալ տեղեկություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածից"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Խոշորացույցի պատուհանի կարգավորումներ"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Հատուկ գործառույթները բացելու համար հպեք։ Անհատականացրեք այս կոճակը կարգավորումներում։\n\n"<annotation id="link">"Կարգավորումներ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Կոճակը ժամանակավորապես թաքցնելու համար այն տեղափոխեք էկրանի եզր"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Հետարկել"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} դյուրանցում հեռացվեց}one{# դյուրանցում հեռացվեց}other{# դյուրանցում հեռացվեց}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Տեղափոխել վերև՝ ձախ"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Տեղափոխել վերև՝ աջ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Տեղափոխել ներքև՝ ձախ"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Տեղափոխել ներքև՝ աջ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Տեղափոխել եզրից դուրս և թաքցնել"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Տեղափոխել եզրից դուրս և ցուցադրել"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Հեռացնել"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"միացնել/անջատել"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Սարքերի կառավարման տարրեր"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Ընտրեք հավելված` կառավարման տարրեր ավելացնելու համար"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Բջջային ինտերնետ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Միացած է"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ժամանակավոր կապ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Թույլ կապ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Բջջային ինտերնետն ավտոմատ չի միանա"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Կապ չկա"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Այլ հասանելի ցանցեր չկան"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 87496cf..236ad22 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Tidak mengenali wajah"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan sidik jari"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth terhubung."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Persentase baterai tidak diketahui."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Terhubung ke <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kelola pengguna"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tutup"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Terhubung"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon tersedia"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera tersedia"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon dan kamera tersedia"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon diaktifkan"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon dinonaktifkan"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon diaktifkan untuk semua aplikasi dan layanan."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Akses mikrofon dinonaktifkan untuk semua aplikasi dan layanan. Anda dapat mengaktifkan akses mikrofon di Setelan > Privasi > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Akses mikrofon dinonaktifkan untuk semua aplikasi dan layanan. Anda dapat mengubahnya di Setelan > Privasi > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera diaktifkan"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera dinonaktifkan"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera diaktifkan untuk semua aplikasi dan layanan."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Akses kamera dinonaktifkan untuk semua aplikasi dan layanan."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Untuk menggunakan tombol mikrofon, aktifkan akses mikrofon di Setelan."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Buka setelan."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Perangkat lainnya"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aktifkan Ringkasan"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Anda tidak akan terganggu oleh suara dan getaran, kecuali dari alarm, pengingat, acara, dan penelepon yang Anda tentukan. Anda akan tetap mendengar apa pun yang telah dipilih untuk diputar, termasuk musik, video, dan game."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Lanjutkan"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Bagikan atau rekam aplikasi"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Nonaktifkan data seluler?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan dapat mengakses data atau internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya akan tersedia melalui Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Operator Seluler Anda"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Beralih kembali ke <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data seluler tidak akan dialihkan secara otomatis berdasarkan ketersediaan"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Lain kali"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ya, alihkan"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Karena sebuah aplikasi menghalangi permintaan izin, Setelan tidak dapat memverifikasi respons Anda."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Izinkan <xliff:g id="APP_0">%1$s</xliff:g> menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Dapat membaca informasi dari <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setelan jendela kaca pembesar"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketuk untuk membuka fitur aksesibilitas. Sesuaikan atau ganti tombol ini di Setelan.\n\n"<annotation id="link">"Lihat setelan"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pindahkan tombol ke tepi agar tersembunyi untuk sementara"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Urungkan"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Pintasan {label} dihapus}other{# pintasan dihapus}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pindahkan ke kiri atas"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pindahkan ke kanan atas"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pindahkan ke kiri bawah"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pindahkan ke kanan bawah"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pindahkan ke tepi dan sembunyikan"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pindahkan dari tepi dan tampilkan"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Hapus"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alihkan"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrol perangkat"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Pilih aplikasi untuk menambahkan kontrol"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Data seluler"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Terhubung"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Terhubung sementara"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Koneksi buruk"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Data seluler tidak akan terhubung otomatis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Tidak ada koneksi"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Jaringan lain tidak tersedia"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 3d7e2ea..537af36 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Andlit þekkist ekki"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Nota fingrafar í staðinn"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tengt."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Staða rafhlöðu óþekkt."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tengt við <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Stjórna notendum"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Lokið"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Loka"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Tengt"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Hljóðnemi tiltækur"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Myndavél tiltæk"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Hljóðnemi og myndavél tiltæk"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Kveikt á hljóðnemanum"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Slökkt á hljóðnemanum"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Kveikt er á hljóðnema fyrir öll forrit og þjónustur."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Slökkt er á aðgangi að hljóðnema fyrir öll forrit og þjónustur. Þú getur veitt aðgang að hljóðnema í „Stillingar > Persónuvernd > Hljóðnemi“."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Slökkt er á aðgangi að hljóðnema fyrir öll forrit og þjónustur. Þú getur breytt þessu í „Stillingar > Persónuvernd > Hljóðnemi“."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kveikt á myndavél"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Slökkt á myndavél"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kveikt er á myndavél fyrir öll forrit og þjónustur."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Slökkt er á aðgangi að myndavél fyrir öll forrit og þjónustur."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Veittu aðgang að hljóðnema í stillingunum til að nota hljóðnemahnappinn."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Opna stillingar."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Annað tæki"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Kveikja/slökkva á yfirliti"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Þú verður ekki fyrir truflunum frá hljóðmerkjum og titringi, fyrir utan vekjara, áminningar, viðburði og símtöl frá þeim sem þú leyfir fyrirfram. Þú heyrir áfram í öllu sem þú velur að spila, svo sem tónlist, myndskeiðum og leikjum."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Þegar þú deilir, tekur upp eða sendir út forrit hefur <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Áfram"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deila eða taka upp forrit"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Viltu slökkva á farsímagögnum?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Þú munt ekki hafa aðgang að gögnum eða internetinu í gegnum <xliff:g id="CARRIER">%s</xliff:g>. Aðeins verður hægt að tengjast internetinu með Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"símafyrirtækið þitt"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Skipta aftur yfir í <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Ekki verður skipt sjálfkrafa á milli farsímagagna byggt á tiltækileika"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nei takk"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Já, skipta"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Stillingar geta ekki staðfest svarið þitt vegna þess að forrit er að fela heimildarbeiðni."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Viltu leyfa <xliff:g id="APP_0">%1$s</xliff:g> að sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Það getur lesið upplýsingar úr <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Stillingar stækkunarglugga"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ýttu til að opna aðgengiseiginleika. Sérsníddu eða skiptu hnappinum út í stillingum.\n\n"<annotation id="link">"Skoða stillingar"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Færðu hnappinn að brúninni til að fela hann tímabundið"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Afturkalla"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} flýtileið fjarlægð}one{# flýtileið fjarlægð}other{# flýtileiðir fjarlægðar}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Færa efst til vinstri"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Færa efst til hægri"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Færa neðst til vinstri"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Færa neðst til hægri"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Færa að jaðri og fela"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Færa að jaðri og birta"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Fjarlægja"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"kveikja/slökkva"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Tækjastjórnun"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Veldu forrit til að bæta við stýringum"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Farsímagögn"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Tengt"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tímabundin tenging"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Léleg tenging"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Farsímagögn tengjast ekki sjálfkrafa"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Engin tenging"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Engin önnur net í boði"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 1115680..255885e 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Volto non riconosciuto"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa l\'impronta"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth collegato."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentuale della batteria sconosciuta."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connesso a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestisci utenti"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Fine"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Chiudi"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Connesso"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfono disponibile"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Fotocamera disponibile"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Fotocamera e microfono disponibili"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfono attivo"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfono non attivo"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Il microfono è attivo per tutti i servizi e le app."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"L\'accesso al microfono è disattivato per tutti i servizi e le app. Puoi attivarlo in Impostazioni > Privacy > Microfono."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"L\'accesso al microfono è disattivato per tutti i servizi e le app. Puoi modificare questa preferenza in Impostazioni > Privacy > Microfono."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Fotocamera attiva"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Fotocamera non attiva"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"La fotocamera è attiva per tutti i servizi e le app."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"L\'accesso alla fotocamera è disattivato per tutti i servizi e le app."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Per usare il pulsante del microfono devi attivare l\'accesso al microfono nelle Impostazioni."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Apri le impostazioni"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Altro dispositivo"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Attiva/disattiva la panoramica"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Non verrai disturbato da suoni e vibrazioni, ad eccezione di sveglie, promemoria, eventi, chiamate da contatti da te specificati ed elementi che hai scelto di continuare a riprodurre, inclusi video, musica e giochi."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando condividi, registri o trasmetti un\'app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Condividi o registra un\'app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestisci"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Cronologia"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Disattivare i dati mobili?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Non avrai accesso ai dati o a Internet tramite <xliff:g id="CARRIER">%s</xliff:g>. Internet sarà disponibile soltanto tramite Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"il tuo operatore"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vuoi passare nuovamente all\'operatore <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"I dati mobili non passeranno automaticamente all\'operatore in base alla disponibilità"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, grazie"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sì, confermo"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Un\'app sta oscurando una richiesta di autorizzazione, pertanto Impostazioni non può verificare la tua risposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vuoi consentire all\'app <xliff:g id="APP_0">%1$s</xliff:g> di mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Può leggere informazioni dell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Impostazioni della finestra di ingrandimento"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tocca per aprire funzioni di accessibilità. Personalizza o sostituisci il pulsante in Impostazioni.\n\n"<annotation id="link">"Vedi impostazioni"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sposta il pulsante fino al bordo per nasconderlo temporaneamente"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Elimina"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} scorciatoia rimossa}many{# scorciatoie rimosse}other{# scorciatoie rimosse}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sposta in alto a sinistra"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sposta in alto a destra"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sposta in basso a sinistra"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sposta in basso a destra"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Sposta fino a bordo e nascondi"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Sposta fuori da bordo e mostra"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Rimuovi"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"attiva/disattiva"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controllo dispositivi"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Scegli un\'app per aggiungere controlli"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dati mobili"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connessione attiva"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connessa temporaneamente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connessione debole"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nessuna connessione dati mobili automatica"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nessuna connessione"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nessun\'altra rete disponibile"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 1fe4e3e..8611c272 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"לא ניתן לזהות את הפנים"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"שימוש בטביעת אצבע במקום זאת"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth מחובר."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"אחוז טעינת הסוללה לא ידוע."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"התבצע חיבור אל <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"המיקרופון זמין"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"המצלמה זמינה"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"המיקרופון והמצלמה זמינים"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"המיקרופון פועל"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"המיקרופון כבוי"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"הגישה למיקרופון הופעלה לכל האפליקציות והשירותים."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"הגישה למיקרופון הושבתה לכל האפליקציות והשירותים. אפשר להפעיל את הגישה למיקרופון ב\'הגדרות\' > \'פרטיות\' > \'מיקרופון\'."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"הגישה למיקרופון הושבתה לכל האפליקציות והשירותים. אפשר לשנות את הגישה ב\'הגדרות\' > \'פרטיות\' > \'מיקרופון\'."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"המצלמה פועלת"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"המצלמה כבויה"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"הגישה למצלמה הופעלה לכל האפליקציות והשירותים."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"הגישה למצלמה הושבתה לכל האפליקציות והשירותים."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"כדי להשתמש בלחצן המיקרופון יש להפעיל את הגישה למיקרופון ב\'הגדרות\'."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"פתיחת ההגדרות."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"מכשיר אחר"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"החלפת מצב של מסכים אחרונים"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"כדי לא להפריע לך, המכשיר לא ירטוט ולא ישמיע שום צליל, חוץ מהתראות, תזכורות, אירועים ושיחות ממתקשרים מסוימים לבחירתך. המצב הזה לא ישפיע על צלילים שהם חלק מתוכן שבחרת להפעיל, כמו מוזיקה, סרטונים ומשחקים."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"המשך"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"שיתוף או הקלטה של אפליקציה"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"היסטוריה"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"לכבות את חבילת הגלישה?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"לא תהיה לך גישה לנתונים או לאינטרנט באמצעות <xliff:g id="CARRIER">%s</xliff:g>. אינטרנט יהיה זמין רק באמצעות Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"הספק שלך"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"לחזור אל <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"לא תתבצע החלפה אוטומטית של חבילת הגלישה על סמך זמינות"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"לא, תודה"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"כן, אני רוצה להחליף"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"יש אפליקציה שמסתירה את בקשת ההרשאה, ולכן אין אפשרות לאמת את התשובה בהגדרות."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"האם לאפשר ל-<xliff:g id="APP_0">%1$s</xliff:g> להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- תהיה לה אפשרות לקרוא מידע מאפליקציית <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ההגדרות של חלון ההגדלה"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"מקישים כדי לפתוח את תכונות הנגישות. אפשר להחליף את הלחצן או להתאים אותו אישית בהגדרות.\n\n"<annotation id="link">"הצגת ההגדרות"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"כדי להסתיר זמנית את הלחצן, יש להזיז אותו לקצה"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ביטול"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{קיצור הדרך אל {label} הוסר}two{# קיצורי דרך הוסרו}many{# קיצורי דרך הוסרו}other{# קיצורי דרך הוסרו}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"העברה לפינה השמאלית העליונה"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"העברה לפינה הימנית העליונה"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"העברה לפינה השמאלית התחתונה"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"העברה לפינה הימנית התחתונה"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"העברה לשוליים והסתרה"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"העברה מהשוליים והצגה"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"הסרה"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"החלפת מצב"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"פקדי מכשירים"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"יש לבחור אפליקציה כדי להוסיף פקדים"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"חבילת גלישה"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"מחובר"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"מחובר באופן זמני"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"חיבור באיכות ירודה"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"החיבור לנתונים סלולריים לא מתבצע באופן אוטומטי"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"אין חיבור"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"אין רשתות זמינות אחרות"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index b00ceb4..b0d5437 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"顔を認識できません"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"指紋認証をお使いください"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetoothに接続済み。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"バッテリー残量は不明です。"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>に接続しました。"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ユーザーを管理"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"完了"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"閉じる"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"接続済み"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"マイクを利用できます"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"カメラを利用できます"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"マイクとカメラを利用できます"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"マイクを ON にしました"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"マイクを OFF にしました"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"マイクはすべてのアプリとサービスで有効になっています。"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"マイクへのアクセスは、すべてのアプリとサービスで無効になっています。[設定] > [プライバシー] > [マイク] で有効にできます。"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"マイクへのアクセスは、すべてのアプリとサービスで無効になっています。この設定は、[設定] > [プライバシー] > [マイク] で変更できます。"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"カメラを ON にしました"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"カメラを OFF にしました"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"カメラはすべてのアプリとサービスで有効になっています。"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"カメラへのアクセスは、すべてのアプリとサービスで無効になっています。"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"マイクボタンを使用するには、[設定] でマイクへのアクセスを有効にしてください。"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"設定を開く"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"その他のデバイス"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"概要を切り替え"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"アラーム、リマインダー、予定、指定した人からの着信以外の音やバイブレーションに煩わされることはありません。音楽、動画、ゲームなど再生対象として選択したコンテンツは引き続き再生されます。"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"アプリの共有、録画、キャスト中は、そのアプリで表示されている内容や再生している内容に <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> がアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"続行"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"アプリの共有、録画"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"履歴"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"モバイルデータを OFF にしますか?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>でデータやインターネットにアクセスできなくなります。インターネットには Wi-Fi からのみ接続できます。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"携帯通信会社"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> に戻しますか?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"利用可能な場合でも、モバイルデータを利用するよう自動的に切り替わることはありません"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"キャンセル"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"切り替える"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"アプリが許可リクエストを隠しているため、設定側でユーザーの応答を確認できません。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"「<xliff:g id="APP_2">%2$s</xliff:g>」のスライスの表示を「<xliff:g id="APP_0">%1$s</xliff:g>」に許可しますか?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 「<xliff:g id="APP">%1$s</xliff:g>」からの情報の読み取り"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"拡大鏡ウィンドウの設定"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"タップしてユーザー補助機能を開きます。ボタンのカスタマイズや入れ替えを [設定] で行えます。\n\n"<annotation id="link">"設定を表示"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ボタンを一時的に非表示にするには、端に移動させてください"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"元に戻す"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} 個のショートカットを削除}other{# 個のショートカットを削除}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"左上に移動"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"右上に移動"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"左下に移動"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"右下に移動"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"端に移動して非表示"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"端から移動して表示"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"削除"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切り替え"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"デバイス コントロール"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"コントロールを追加するアプリの選択"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"モバイルデータ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"接続済み"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"一時的に接続されています"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"接続が不安定です"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"モバイルデータには自動接続しません"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"接続なし"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"利用できるネットワークはありません"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index aab9a32..f7725ae 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"სახის ამოცნობა შეუძლებ."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"გამოიყენეთ თითის ანაბეჭდი"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth დაკავშირებულია."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ბატარეის პროცენტული მაჩვენებელი უცნობია."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"დაკავშირებულია <xliff:g id="BLUETOOTH">%s</xliff:g>-თან."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"მომხმარებლების მართვა"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"დასრულდა"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"დახურვა"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"დაკავშირებულია"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"მიკროფონი ხელმისაწვდომია"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"კამერა ხელმისაწვდომია"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"მიკროფონი და კამერა ხელმისაწვდომია"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"მიკროფონი ჩართულია"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"მიკროფონი გამორთულია"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"მიკროფონი ჩართულია ყველა აპისა და სერვისისთვის."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"მიკროფონზე წვდომა გათიშულია ყველა აპისა და სერვისისთვის. მიკროფონზე წვდომის ჩართვა შეგიძლიათ აქედან: პარამეტრები > კონფიდენციალურობა > მიკროფონი."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"მიკროფონზე წვდომა გათიშულია ყველა აპისა და სერვისისთვის. ამის შეცვლა შეგიძლიათ აქედან: პარამეტრები > კონფიდენციალურობა > მიკროფონი."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"კამერა ჩაირთო"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"კამერა გამოირთო"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"კამერა ჩართულია ყველა აპისა და სერვისისთვის."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"კამერაზე წვდომა გათიშულია ყველა აპისა და სერვისისთვის."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"მიკროფონის ღილაკის გამოსაყენებლად, ჩართეთ მიკროფონზე წვდომა პარამეტრებიდან."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"პარამეტრების გახსნა."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"სხვა მოწყობილობა"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"მიმოხილვის გადართვა"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"თქვენ მიერ მითითებული მაღვიძარების, შეხსენებების, მოვლენებისა და ზარების გარდა, არავითარი ხმა და ვიბრაცია არ შეგაწუხებთ. თქვენ მაინც შეძლებთ სასურველი კონტენტის, მაგალითად, მუსიკის, ვიდეოებისა და თამაშების აუდიოს მოსმენა."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> აქვს წვდომა აქვს ყველაფერზე, რაც ჩანს აპში ან ითამაშეთ. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"გაგრძელება"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"გააზიარეთ ან ჩაწერეთ აპი"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"მართვა"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ისტორია"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"გსურთ მობილური ინტერნეტის გამორთვა?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"თქვენ არ გექნებათ მობილურ ინტერნეტზე ან ზოგადად ინტერნეტზე წვდომა <xliff:g id="CARRIER">%s</xliff:g>-ის მეშვეობით. ინტერნეტი მხოლოდ Wi-Fi-კავშირის მეშვეობით იქნება ხელმისაწვდომი."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"თქვენი ოპერატორი"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"გსურთ ისევ <xliff:g id="CARRIER">%s</xliff:g>-ზე გადართვა?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"მობილური მონაცემების ხელმისაწვდომობის მიხედვით ავტომატური გადართვა არ მოხდება"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"არა, გმადლობთ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"დიახ, გადაირთოს"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ვინაიდან აპი ფარავს ნებართვის მოთხოვნას, პარამეტრების მიერ თქვენი პასუხი ვერ დასტურდება."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"ანიჭებთ ნებართვას <xliff:g id="APP_0">%1$s</xliff:g>-ს, აჩვენოს <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- მას შეუძლია ინფორმაციის <xliff:g id="APP">%1$s</xliff:g>-დან წაკითხვა"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"გადიდების ფანჯრის პარამეტრები"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"შეეხეთ მარტივი წვდომის ფუნქციების გასახსნელად. მოარგეთ ან შეცვალეთ ეს ღილაკი პარამეტრებში.\n\n"<annotation id="link">"პარამეტრების ნახვა"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"გადაიტანეთ ღილაკი კიდეში, რათა დროებით დამალოთ ის"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"მოქმედების გაუქმება"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} მალსახმობი ამოშლილია}other{# მალსახმობი ამოშლილია}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ზევით და მარცხნივ გადატანა"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ზევით და მარჯვნივ გადატანა"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ქვევით და მარცხნივ გადატანა"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ქვემოთ და მარჯვნივ გადატანა"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"კიდეში გადატანა და დამალვა"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"კიდეში გადატანა და გამოჩენა"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"წაშლა"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"გადართვა"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"მოწყობილ. მართვის საშუალებები"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"აირჩიეთ აპი მართვის საშუალებების დასამატებლად"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"მობილური ინტერნეტი"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"დაკავშირებული"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"დროებით დაკავშირებული"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"სუსტი კავშირი"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"მობილურ ინტერნეტს ავტომატურად არ დაუკავშირდება"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"კავშირი არ არის"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"სხვა ქსელები მიუწვდომელია"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 19bfdf5..ac77719 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Бет танылмады."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Орнына саусақ ізін пайдаланыңыз."</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Пайдаланушыларды басқару"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Дайын"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Жабу"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Қосылды"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон қолжетімді"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера қолжетімді"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон мен камера қолжетімді"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофон қосулы"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофон өшірулі"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофон барлық қолданба мен қызмет үшін қосулы."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Микрофон пайдалану рұқсаты барлық қолданба мен қызмет үшін өшірулі. Микрофон пайдалану рұқсатын \"Параметрлер> Құпиялылық > Микрофон\" тармағынан қоса аласыз."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Микрофон пайдалану рұқсаты барлық қолданба мен қызмет үшін өшірулі. Мұны \"Параметрлер > Құпиялылық > Микрофон\" тармағынан өзгерте аласыз."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера қосулы"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера өшірулі"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера барлық қолданба мен қызмет үшін қосулы."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Камера пайдалану рұқсаты барлық қолданба мен қызмет үшін өшірулі."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Микрофон түймесін пайдалану үшін параметрлерден микрофон пайдалану рұқсатын қосыңыз."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Параметрлерді ашу."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Басқа құрылғы"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Шолуды қосу/өшіру"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Оятқыш, еске салғыш, іс-шара мен өзіңіз көрсеткен контактілердің қоңырауларынан басқа дыбыстар мен дірілдер мазаламайтын болады. Музыка, бейне және ойын сияқты медиафайлдарды қоссаңыз, оларды естисіз."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Жалғастыру"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Қолданба экранын бөлісу не жазу"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік интернет өшірілсін бе?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> операторы арқылы деректерге немесе интернетке кіре алмайсыз. Интернетке тек Wi-Fi арқылы кіресіз."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"операторыңыз"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> операторына қайта ауысу керек пе?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобильдік интернет операторды қолдану мүмкіндігіне қарай автоматты түрде ауыспайды."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Жоқ, рақмет"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Иә, ауыстырылсын"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Басқа қолданба рұқсат сұрауын жасырып тұрғандықтан, параметрлер жауабыңызды растай алмайды."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасына <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсетуге рұқсат берілсін бе?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Бұл <xliff:g id="APP">%1$s</xliff:g> қолданбасындағы ақпаратты оқи алады"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ұлғайтқыш терезесінің параметрлері"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Арнайы мүмкіндікті ашу үшін түртіңіз. Түймені параметрден реттеңіз не ауыстырыңыз.\n\n"<annotation id="link">"Параметрді көру"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Түймені уақытша жасыру үшін оны шетке қарай жылжытыңыз."</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Қайтару"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} таңбаша өшірілді.}other{# таңбаша өшірілді.}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жоғарғы сол жаққа жылжыту"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Жоғарғы оң жаққа жылжыту"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Төменгі сол жаққа жылжыту"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Төменгі оң жаққа жылжыту"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Шетке жылжыту және жасыру"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Шетке жылжыту және көрсету"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Өшіру"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ауыстыру"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Құрылғыны басқару элементтері"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Басқару элементтері қосылатын қолданбаны таңдаңыз"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильдік интернет"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Жалғанды"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Уақытша байланыс орнатылды."</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Байланыс нашар."</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобильдік интернет автоматты түрде қосылмайды."</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыс жоқ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Басқа қолжетімді желі жоқ"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 186244b..6504990 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"មិនអាចសម្គាល់មុខបានទេ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ប្រើស្នាមម្រាមដៃជំនួសវិញ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"បានតភ្ជាប់ប៊្លូធូស។"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"មិនដឹងអំពីភាគរយថ្មទេ។"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"បានភ្ជាប់ទៅ <xliff:g id="BLUETOOTH">%s</xliff:g> ។"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"គ្រប់គ្រងអ្នកប្រើប្រាស់"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"រួចរាល់"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"បិទ"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"បានភ្ជាប់"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"អាចប្រើមីក្រូហ្វូនបាន"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"អាចប្រើកាមេរ៉ាបាន"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"អាចប្រើកាមេរ៉ា និងមីក្រូហ្វូនបាន"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"បានបើកមីក្រូហ្វូន"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"បានបិទមីក្រូហ្វូន"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"មីក្រូហ្វូនត្រូវបានបើកសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"សិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូនត្រូវបានបិទសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។ អ្នកអាចបើកសិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូននៅក្នុងការកំណត់ > ឯកជនភាព > មីក្រូហ្វូន។"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"សិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូនត្រូវបានបិទសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។ អ្នកអាចផ្លាស់ប្ដូរលក្ខណៈនេះនៅក្នុងការកំណត់ > ឯកជនភាព > មីក្រូហ្វូន។"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"បានបើកកាមេរ៉ា"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"បានបិទកាមេរ៉ា"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"កាមេរ៉ាត្រូវបានបើកសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"សិទ្ធិចូលប្រើប្រាស់កាមេរ៉ាត្រូវបានបិទសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ដើម្បីប្រើប្រាស់ប៊ូតុងមីក្រូហ្វូន សូមបើកសិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូននៅក្នុងការកំណត់។"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"បើកការកំណត់។"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"ឧបករណ៍ផ្សេងទៀត"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"បិទ/បើកទិដ្ឋភាពរួម"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"សំឡេង និងរំញ័រនឹងមិនរំខានដល់អ្នកឡើយ លើកលែងតែម៉ោងរោទ៍ ការរំលឹក ព្រឹត្តិការណ៍ និងអ្នកហៅទូរសព្ទដែលអ្នកបញ្ជាក់ប៉ុណ្ណោះ។ អ្នកនឹងនៅតែឮសំឡេងសកម្មភាពគ្រប់យ៉ាងដែលអ្នកលេងដដែល រួមទាំងតន្រ្តី វីដេអូ និងហ្គេម។"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូនកម្មវិធី <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញ ឬលេងនៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"បន្ត"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ចែករំលែក ឬថតកម្មវិធី"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាតទាំងអស់"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"គ្រប់គ្រង"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ប្រវត្តិ"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"បិទទិន្នន័យទូរសព្ទចល័ត?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"អ្នកនឹងមិនមានសិទ្ធិចូលប្រើទិន្នន័យ ឬអ៊ីនធឺណិតតាមរយៈ <xliff:g id="CARRIER">%s</xliff:g> បានឡើយ។ អ៊ីនធឺណិតនឹងអាចប្រើបានតាមរយៈ Wi-Fi តែប៉ុណ្ណោះ។"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ក្រុមហ៊ុនសេវាទូរសព្ទរបស់អ្នក"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ប្ដូរទៅ <xliff:g id="CARRIER">%s</xliff:g> វិញឬ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ទិន្នន័យទូរសព្ទចល័តនឹងមិនប្ដូរដោយស្វ័យប្រវត្តិដោយផ្អែកតាមភាពអាចប្រើបាននោះទេ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ទេ អរគុណ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"បាទ/ចាស ប្ដូរ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ការកំណត់មិនអាចផ្ទៀងផ្ទាត់ការឆ្លើយតបរបស់អ្នកបានទេ ដោយសារកម្មវិធីកំពុងបាំងសំណើសុំការអនុញ្ញាត។"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"អនុញ្ញាតឱ្យ <xliff:g id="APP_0">%1$s</xliff:g> បង្ហាញស្ថិតិប្រើប្រាស់របស់ <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- វាអាចអានព័ត៌មានពី <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ការកំណត់វិនដូកម្មវិធីពង្រីក"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ចុចដើម្បីបើកមុខងារភាពងាយស្រួល។ ប្ដូរ ឬប្ដូរប៊ូតុងនេះតាមបំណងនៅក្នុងការកំណត់។\n\n"<annotation id="link">"មើលការកំណត់"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ផ្លាស់ទីប៊ូតុងទៅគែម ដើម្បីលាក់វាជាបណ្ដោះអាសន្ន"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ត្រឡប់វិញ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{បានដកផ្លូវកាត់ {label} ចេញ}other{បានដកផ្លូវកាត់ # ចេញ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងឆ្វេង"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងស្ដាំ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ផ្លាស់ទីទៅខាងក្រោមផ្នែកខាងឆ្វេង"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ផ្លាស់ទីទៅខាងក្រោមផ្នែកខាងស្ដាំ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ផ្លាស់ទីទៅផ្នែកខាងចុង រួចលាក់"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ផ្លាស់ទីចេញពីផ្នែកខាងចុង រួចបង្ហាញ"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ដកចេញ"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"បិទ/បើក"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ផ្ទាំងគ្រប់គ្រងឧបករណ៍"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"ជ្រើសរើសកម្មវិធីដែលត្រូវបញ្ចូលផ្ទាំងគ្រប់គ្រង"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ទិន្នន័យទូរសព្ទចល័ត"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"បានភ្ជាប់"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"បានភ្ជាប់ជាបណ្ដោះអាសន្ន"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ការតភ្ជាប់ខ្សោយ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ទិន្នន័យទូរសព្ទចល័តនឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិទេ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"មិនមានការតភ្ជាប់ទេ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"មិនមានបណ្ដាញផ្សេងទៀតដែលអាចប្រើបានទេ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 3341f8d..e1124bb 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ಬದಲಿಗೆ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಬಳಸಿ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ಬ್ಲೂಟೂತ್ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ಬ್ಯಾಟರಿ ಶೇಕಡಾವಾರು ತಿಳಿದಿಲ್ಲ."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ಬಳಕೆದಾರರನ್ನು ನಿರ್ವಹಿಸಿ"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ಮುಗಿದಿದೆ"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ಮುಚ್ಚಿರಿ"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ಮೈಕ್ರೊಫೋನ್ ಲಭ್ಯವಿದೆ"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ಕ್ಯಾಮರಾ ಲಭ್ಯವಿದೆ"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಕ್ಯಾಮರಾ ಲಭ್ಯವಿದೆ"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಸೆಟ್ಟಿಂಗ್ಗಳು > ಗೌಪ್ಯತೆ > ಮೈಕ್ರೊಫೋನ್ ಎಂಬಲ್ಲಿಗೆ ಹೋಗುವ ಮೂಲಕ ನೀವು ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಸೆಟ್ಟಿಂಗ್ಗಳು > ಗೌಪ್ಯತೆ > ಮೈಕ್ರೊಫೋನ್ ಎಂಬಲ್ಲಿಗೆ ಹೋಗುವ ಮೂಲಕ ನೀವು ಇದನ್ನು ಬದಲಾಯಿಸಬಹುದು."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ಕ್ಯಾಮರಾವನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ಕ್ಯಾಮರಾವನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಕ್ಯಾಮರಾವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಕ್ಯಾಮರಾ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ಮೈಕ್ರೊಫೋನ್ ಬಟನ್ ಅನ್ನು ಬಳಸಲು, ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ತೆರೆಯಿರಿ."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"ಅನ್ಯ ಸಾಧನ"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ಟಾಗಲ್ ನ ಅವಲೋಕನ"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"ಅಲಾರಾಂಗಳು, ಜ್ಞಾಪನೆಗಳು, ಈವೆಂಟ್ಗಳು ಹಾಗೂ ನೀವು ಸೂಚಿಸಿರುವ ಕರೆದಾರರನ್ನು ಹೊರತುಪಡಿಸಿ ಬೇರಾವುದೇ ಸದ್ದುಗಳು ಅಥವಾ ವೈಬ್ರೇಶನ್ಗಳು ನಿಮಗೆ ತೊಂದರೆ ನೀಡುವುದಿಲ್ಲ. ಹಾಗಿದ್ದರೂ, ನೀವು ಪ್ಲೇ ಮಾಡುವ ಸಂಗೀತ, ವೀಡಿಯೊಗಳು ಮತ್ತು ಆಟಗಳ ಆಡಿಯೊವನ್ನು ನೀವು ಕೇಳಿಸಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ಮುಂದುವರಿಸಿ"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ಮೊಬೈಲ್ ಡೇಟಾ ಆಫ್ ಮಾಡಬೇಕೆ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ನೀವು <xliff:g id="CARRIER">%s</xliff:g> ಮೂಲಕ ಡೇಟಾ ಅಥವಾ ಇಂಟರ್ನೆಟ್ಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುವುದಿಲ್ಲ. ಇಂಟರ್ನೆಟ್, ವೈ-ಫೈ ಮೂಲಕ ಮಾತ್ರ ಲಭ್ಯವಿರುತ್ತದೆ."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ನಿಮ್ಮ ವಾಹಕ"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> ಗೆ ಬದಲಿಸುವುದೇ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ಲಭ್ಯತೆಯ ಆಧಾರದ ಮೇಲೆ ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಬದಲಾಗುವುದಿಲ್ಲ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ಬೇಡ, ಧನ್ಯವಾದಗಳು"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ಹೌದು, ಬದಲಿಸಿ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ, ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ಮ್ಯಾಗ್ನಿಫೈರ್ ವಿಂಡೋ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಈ ಬಟನ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ ಅಥವಾ ಬದಲಾಯಿಸಿ.\n\n"<annotation id="link">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ಅದನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಮರೆಮಾಡಲು ಅಂಚಿಗೆ ಬಟನ್ ಸರಿಸಿ"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ರದ್ದುಗೊಳಿಸಿ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}one{# ಶಾರ್ಟ್ಕಟ್ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}other{# ಶಾರ್ಟ್ಕಟ್ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ಎಡ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ಬಲ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ಸ್ಕ್ರೀನ್ನ ಎಡ ಕೆಳಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ಅಂಚಿಗೆ ಸರಿಸಿ ಮತ್ತು ಮರೆಮಾಡಿ"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ಅಂಚನ್ನು ಸರಿಸಿ ಮತ್ತು ತೋರಿಸಿ"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ಟಾಗಲ್ ಮಾಡಿ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ಸಾಧನ ನಿಯಂತ್ರಣಗಳು"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ಮೊಬೈಲ್ ಡೇಟಾ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ತಾತ್ಕಾಲಿಕವಾಗಿ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ಕಳಪೆ ಸಂಪರ್ಕ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕನೆಕ್ಟ್ ಆಗುವುದಿಲ್ಲ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ಯಾವುದೇ ಕನೆಕ್ಷನ್ ಇಲ್ಲ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ಇತರ ಯಾವುದೇ ನೆಟ್ವರ್ಕ್ಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 783365c..e5e4657 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"얼굴을 인식할 수 없습니다."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"대신 지문을 사용하세요."</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"블루투스가 연결되었습니다."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"배터리 잔량을 알 수 없습니다."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>에 연결되었습니다."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"사용자 관리"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"완료"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"닫기"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"연결됨"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"마이크 사용 가능"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"카메라 사용 가능"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"마이크 및 카메라 사용 가능"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"마이크 사용 설정됨"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"마이크 사용 중지됨"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"모든 앱 및 서비스의 마이크가 사용 설정됩니다."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"모든 앱 및 서비스의 마이크 액세스가 사용 중지됩니다. 설정 > 개인 정보 보호 > 마이크에서 마이크 액세스를 사용 설정할 수 있습니다."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"모든 앱 및 서비스의 마이크 액세스가 사용 중지됩니다. 설정 > 개인 정보 보호 > 마이크에서 변경할 수 있습니다."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"카메라 사용 설정됨"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"카메라 사용 중지됨"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"모든 앱 및 서비스의 카메라가 사용 설정됩니다."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"모든 앱 및 서비스의 카메라 액세스가 사용 중지됩니다."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"마이크 버튼을 사용하려면 설정에서 마이크 액세스를 사용 설정하세요."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"설정 열기"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"기타 기기"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"최근 사용 버튼 전환"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"알람, 알림, 일정 및 지정한 발신자로부터 받은 전화를 제외한 소리와 진동을 끕니다. 음악, 동영상, 게임 등 재생하도록 선택한 소리는 정상적으로 들립니다."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"모바일 데이터를 사용 중지하시겠습니까?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>을(를) 통해 데이터 또는 인터넷에 액세스할 수 없게 됩니다. 인터넷은 Wi-Fi를 통해서만 사용할 수 있습니다."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"이동통신사"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"다시 <xliff:g id="CARRIER">%s</xliff:g>(으)로 전환할까요?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"모바일 데이터가 가용성에 따라 자동으로 전환하지 않습니다."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"나중에"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"예, 전환합니다"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"앱이 권한 요청을 가리고 있기 때문에 설정에서 내 응답을 확인할 수 없습니다."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하도록 허용하시겠습니까?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g>의 정보를 읽을 수 있음"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"돋보기 창 설정"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"접근성 기능을 열려면 탭하세요. 설정에서 이 버튼을 맞춤설정하거나 교체할 수 있습니다.\n\n"<annotation id="link">"설정 보기"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"버튼을 가장자리로 옮겨서 일시적으로 숨기세요."</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"실행취소"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{바로가기 {label}개 삭제됨}other{바로가기 #개 삭제됨}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"왼쪽 상단으로 이동"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"오른쪽 상단으로 이동"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"왼쪽 하단으로 이동"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"오른쪽 하단으로 이동"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"가장자리로 옮겨서 숨기기"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"가장자리 바깥으로 옮겨서 표시"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"삭제"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"전환"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"기기 컨트롤"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"컨트롤을 추가할 앱을 선택하세요"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"모바일 데이터"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"연결됨"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"일시적으로 연결됨"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"연결 상태 나쁨"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"모바일 데이터가 자동으로 연결되지 않음"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"연결되지 않음"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"사용 가능한 다른 네트워크가 없음"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index b9da9f0..bef48dc 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Жүз таанылбай жатат"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Манжа изин колдонуңуз"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth байланышта"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея кубатынын деңгээли белгисиз."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> менен туташкан."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Колдонуучуларды тескөө"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Бүттү"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Жабуу"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Туташкан"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон жеткиликтүү"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера жеткиликтүү"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон жана камера жеткиликтүү"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофон күйгүзүлдү"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофон өчүрүлдү"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофон бардык колдонмолор жана кызматтар үчүн иштетилди."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Микрофон бардык колдонмолор жана кызматтар үчүн өчүрүлдү. Микрофонду колдонууну иштетүү үчүн > Купуялык > Микрофон бөлүмүнө өтүңүз."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Микрофон бардык колдонмолор жана кызматтар үчүн өчүрүлдү. Муну Параметрлер > Купуялык > Микрофон бөлүмүнөн өзгөртө аласыз."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера күйгүзүлдү"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера өчүрүлдү"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера бардык колдонмолор жана кызматтар үчүн иштетилди."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Камера бардык колдонмолор жана кызматтар үчүн өчүрүлдү."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Микрофон баскычын колдонуу үчүн Параметрлерден микрофонду колдонууну иштетиңиз."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Параметрлерди ачуу."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Башка түзмөк"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Назар режимин өчүрүү/күйгүзүү"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Ойготкучтардан, эскертүүлөрдөн, жылнаамадагы иш-чараларды эстеткичтерден жана белгиленген байланыштардын чалууларынан тышкары башка үндөр жана дирилдөөлөр тынчыңызды албайт. Бирок ойнотулуп жаткан музыканы, видеолорду жана оюндарды мурдагыдай эле уга бересиз."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Бөлүшүп, жаздырып же тышкы экранда бөлүшкөндө <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ал колдонмодо көрүнүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Улантуу"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Колдонмону бөлүшүү же жаздыруу"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Башкаруу"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Таржымал"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобилдик Интернетти өчүрөсүзбү?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> байланыш оператору аркылуу Интернетке кире албай каласыз. Интернетке Wi-Fi аркылуу гана кирүүгө болот."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"байланыш операторуңуз"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Кайра <xliff:g id="CARRIER">%s</xliff:g> байланыш операторуна которуласызбы?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Жеткиликтүү болгондо мобилдик Интернет автоматтык түрдө которулбайт"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Жок, рахмат"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ооба, которулуу"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Уруксат берүү сурамыңыз көрүнбөй калгандыктан, Жөндөөлөр жообуңузду ырастай албай жатат."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Чоңойткуч терезесинин параметрлери"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Атайын мүмкүнчүлүктөрдү ачуу үчүн басыңыз. Бул баскычты Жөндөөлөрдөн өзгөртүңүз.\n\n"<annotation id="link">"Жөндөөлөрдү көрүү"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Баскычты убактылуу жашыра туруу үчүн экрандын четине жылдырыңыз"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Кайтаруу"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ыкчам баскыч өчүрүлдү}other{# ыкчам баскыч өчүрүлдү}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жогорку сол жакка жылдыруу"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Жогорку оң жакка жылдырыңыз"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Төмөнкү сол жакка жылдыруу"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Төмөнкү оң жакка жылдырыңыз"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ичине жылдырып, көрсөтүңүз"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Сыртка жылдырып, көрсөтүңүз"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Өчүрүү"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"өчүрүү/күйгүзүү"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Түзмөктү башкаруу элементтери"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Башкаруу элементтери кошула турган колдонмону тандоо"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилдик трафик"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Туташты"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Убактылуу туташып турат"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Байланыш начар"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилдик трафик автоматтык түрдө туташтырылбайт"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыш жок"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Башка тармактар жеткиликсиз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index d7dbb86..0d0d9de 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ບໍ່ສາມາດຈຳແນກໃບໜ້າໄດ້"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ກະລຸນາໃຊ້ລາຍນິ້ວມືແທນ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ເຊື່ອມຕໍ່ Bluetooth ແລ້ວ."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ບໍ່ຮູ້ເປີເຊັນແບັດເຕີຣີ."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ເຊື່ອມຕໍ່ຫາ <xliff:g id="BLUETOOTH">%s</xliff:g> ແລ້ວ."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ຈັດການຜູ້ໃຊ້"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ແລ້ວໆ"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ປິດ"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ສາມາດໃຊ້ໄມໂຄຣໂຟນໄດ້"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ສາມາດໃຊ້ກ້ອງຖ່າຍຮູບໄດ້"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ສາມາດໃຊ້ໄມໂຄຣໂຟນ ແລະ ກ້ອງຖ່າຍຮູບໄດ້"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ເປີດໄມໂຄຣໂຟນແລ້ວ"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ປິດໄມໂຄຣໂຟນແລ້ວ"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ເປີດການນຳໃຊ້ໄມໂຄຣໂຟນສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນຖືກປິດການນຳໃຊ້ສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ. ທ່ານສາມາດເປີດການນຳໃຊ້ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນໄດ້ໃນການຕັ້ງຄ່າ > ຄວາມເປັນສ່ວນຕົວ > ໄມໂຄຣໂຟນ."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນຖືກປິດການນຳໃຊ້ສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ > ຄວາມເປັນສ່ວນຕົວ > ໄມໂຄຣໂຟນ."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ເປີດກ້ອງຖ່າຍຮູບແລ້ວ"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ປິດກ້ອງຖ່າຍຮູບແລ້ວ"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ເປີດການນຳໃຊ້ກ້ອງຖ່າຍຮູບສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ສິດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຖືກປິດການນຳໃຊ້ສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ເພື່ອໃຊ້ປຸ່ມໄມໂຄຣໂຟນ, ໃຫ້ເປີດການນຳໃຊ້ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນໃນການຕັ້ງຄ່າກ່ອນ."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ເປີດການຕັ້ງຄ່າ."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"ອຸປະກອນອື່ນໆ"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ສະຫຼັບພາບຮວມ"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"ທ່ານຈະບໍ່ໄດ້ຮັບການລົບກວນຈາກສຽງ ແລະ ການສັ່ນເຕືອນ, ຍົກເວັ້ນໃນເວລາໂມງປຸກດັງ, ມີການແຈ້ງເຕືອນ ຫຼື ມີສາຍໂທເຂົ້າຈາກຜູ້ໂທທີ່ທ່ານລະບຸໄວ້. ທ່ານອາດຍັງຄົງໄດ້ຍິນຫາກທ່ານເລືອກຫຼິ້ນເພງ, ວິດີໂອ ແລະ ເກມ."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ໃນຕອນທີ່ທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ມີສິດເຂົ້າເຖິງສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງກ່ຽວກັບລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ສືບຕໍ່"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ແບ່ງປັນ ຫຼື ບັນທຶກແອັບ"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ຈັດການ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ປະຫວັດ"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ປິດອິນເຕີເນັດມືຖືໄວ້ບໍ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ທ່ານຈະບໍ່ມີສິດເຂົ້າເຖິງຂໍ້ມູນ ຫຼື ອິນເຕີເນັດຜ່ານ <xliff:g id="CARRIER">%s</xliff:g>. ອິນເຕີເນັດຈະສາມາດໃຊ້ໄດ້ຜ່ານ Wi-Fi ເທົ່ານັ້ນ."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ຜູ້ໃຫ້ບໍລິການຂອງທ່ານ"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ສະຫຼັບກັບໄປໃຊ້ <xliff:g id="CARRIER">%s</xliff:g> ບໍ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ອິນເຕີເນັດມືຖືຈະບໍ່ປ່ຽນຕາມຄວາມພ້ອມໃຫ້ບໍລິການໂດຍອັດຕະໂນມັດ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ບໍ່, ຂອບໃຈ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ແມ່ນແລ້ວ, ສະຫຼັບ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ເນື່ອງຈາກມີແອັບໃດໜຶ່ງກຳລັງຂັດຂວາງການຂໍອະນຸຍາດ, ການຕັ້ງຄ່າຈຶ່ງບໍ່ສາມາດຢັ້ງຢືນການຕອບຮັບຂອງທ່ານໄດ້."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"ອະນຸຍາດ <xliff:g id="APP_0">%1$s</xliff:g> ໃຫ້ສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້ບໍ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ມັນສາມາດອ່ານຂໍ້ມູນຈາກ <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ການຕັ້ງຄ່າໜ້າຈໍຂະຫຍາຍ"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ແຕະເພື່ອເປີດຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ປັບແຕ່ງ ຫຼື ປ່ຽນປຸ່ມນີ້ໃນການຕັ້ງຄ່າ.\n\n"<annotation id="link">"ເບິ່ງການຕັ້ງຄ່າ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ຍ້າຍປຸ່ມໄປໃສ່ຂອບເພື່ອເຊື່ອງມັນຊົ່ວຄາວ"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ຍົກເລີກ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{ລຶບທາງລັດ {label} ອອກແລ້ວ}other{ລຶບທາງລັດ # ອອກແລ້ວ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ຍ້າຍຊ້າຍເທິງ"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ຍ້າຍຂວາເທິງ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ຍ້າຍຊ້າຍລຸ່ມ"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ຍ້າຍຂວາລຸ່ມ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ຍ້າຍອອກຂອບ ແລະ ເຊື່ອງ"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ຍ້າຍອອກຂອບ ແລະ ສະແດງ"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ລຶບອອກ"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ສະຫຼັບ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ການຄວບຄຸມອຸປະກອນ"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"ເລືອກແອັບເພື່ອເພີ່ມການຄວບຄຸມ"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ອິນເຕີເນັດມືຖື"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ເຊື່ອມຕໍ່ແລ້ວຊົ່ວຄາວ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ສັນຍານເຊື່ອມຕໍ່ຊ້າ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ຈະບໍ່ເຊື່ອມຕໍ່ອິນເຕີເນັດມືຖືອັດຕະໂນມັດ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ບໍ່ມີການເຊື່ອມຕໍ່"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ບໍ່ມີເຄືອຂ່າຍອື່ນທີ່ສາມາດໃຊ້ໄດ້"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index e631ce2..5c9969f 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Veidas neatpažintas"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Naudoti piršto antspaudą"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"„Bluetooth“ prijungtas."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumuliatoriaus energija procentais nežinoma."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Prisijungta prie „<xliff:g id="BLUETOOTH">%s</xliff:g>“."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Tvarkyti naudotojus"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Atlikta"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Uždaryti"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Prijungtas"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofonas pasiekiamas"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Vaizdo kamera pasiekiama"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofonas ir vaizdo kamera pasiekiami"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonas įjungtas"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonas išjungtas"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonas įgalintas veikiant visoms programoms ir paslaugoms."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Visų programų ir paslaugų prieiga prie mikrofono išjungta. Prieigą prie mikrofono galite įjungti nuėję į „Nustatymai“ > „Privatumas“ > „Mikrofonas“."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Visų programų ir paslaugų prieiga prie mikrofono išjungta. Tai galite pakeisti nuėję į „Nustatymai“ > „Privatumas“ > „Mikrofonas“."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Fotoaparatas įjungtas"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Fotoaparatas išjungtas"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Fotoaparatas įgalintas veikiant visoms programoms ir paslaugoms."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Visų programų ir paslaugų prieiga prie fotoaparato išjungta."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Norėdami naudotis mikrofono mygtuku, nustatymuose įjunkite prieigą prie mikrofono."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Atidaryti nustatymus."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Kitas įrenginys"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Perjungti apžvalgą"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Jūsų netrikdys garsai ir vibravimas, išskyrus nurodytų signalų, priminimų, įvykių ir skambintojų garsus. Vis tiek girdėsite viską, ką pasirinksite leisti, įskaitant muziką, vaizdo įrašus ir žaidimus."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kai bendrinate, įrašote ar perduodate turinį, „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tęsti"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Programos bendrinimas ar įrašymas"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Tvarkyti"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Išjungti mobiliojo ryšio duomenis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Naudodamiesi „<xliff:g id="CARRIER">%s</xliff:g>“ paslaugomis neturėsite galimybės pasiekti duomenų arba interneto. Internetą galėsite naudoti tik prisijungę prie „Wi-Fi“."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"savo operatoriaus"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Perjungti atgal į „<xliff:g id="CARRIER">%s</xliff:g>“?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiliojo ryšio duomenys nebus automatiškai perjungti atsižvelgiant į pasiekiamumą"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, ačiū"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Taip, perjungti"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Kadangi programa užstoja leidimo užklausą, nustatymuose negalima patvirtinti jūsų atsakymo."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Leisti „<xliff:g id="APP_0">%1$s</xliff:g>“ rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Gali nuskaityti informaciją iš „<xliff:g id="APP">%1$s</xliff:g>“"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Didinimo lango nustatymai"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Palietę atidarykite pritaikymo neįgaliesiems funkcijas. Tinkinkite arba pakeiskite šį mygtuką nustatymuose.\n\n"<annotation id="link">"Žr. nustatymus"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Perkelkite mygtuką prie krašto, kad laikinai jį paslėptumėte"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anuliuoti"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Pašalintas spartusis klavišas „{label}“}one{Pašalintas # spartusis klavišas}few{Pašalinti # spartieji klavišai}many{Pašalinta # sparčiojo klavišo}other{Pašalinta # sparčiųjų klavišų}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Perkelti į viršų kairėje"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Perkelti į viršų dešinėje"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Perkelti į apačią kairėje"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Perkelti į apačią dešinėje"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Perkelti į kraštą ir slėpti"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Perkelti iš krašto ir rodyti"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Pašalinti"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"perjungti"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Įrenginio valdikliai"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Pasirinkite programą, kad pridėtumėte valdiklių"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiliojo ryšio duomenys"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Prisijungta"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Laikinai prijungta"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Prastas ryšys"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Naud. mob. r. duomenis nebus autom. prisijungiama"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nėra ryšio"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nėra kitų pasiekiamų tinklų"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 0a08654..415ba8f9 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nevar atpazīt seju"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Lietot pirksta nospiedumu"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth savienojums ir izveidots."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumulatora uzlādes līmenis procentos nav zināms."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ir izveidots savienojum ar <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Pārvaldīt lietotājus"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Gatavs"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Aizvērt"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Pievienota"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofons ir pieejams."</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera ir pieejama."</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofons un kamera ir pieejami."</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofons tika ieslēgts"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofons tika izslēgts"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Piekļuve mikrofonam ir iespējota visām lietotnēm un pakalpojumiem."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Piekļuve mikrofonam ir atspējota visām lietotnēm un pakalpojumiem. Varat iespējot piekļuvi mikrofonam sadaļā Iestatījumi > Konfidencialitāte > Mikrofons."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Piekļuve mikrofonam ir atspējota visām lietotnēm un pakalpojumiem. Varat to mainīt sadaļā Iestatījumi > Konfidencialitāte > Mikrofons."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera tika ieslēgta"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera tika izslēgta"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Piekļuve kamerai ir iespējota visām lietotnēm un pakalpojumiem."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Piekļuve kamerai ir atspējota visām lietotnēm un pakalpojumiem."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Lai varētu izmantot mikrofona pogu, iespējojiet piekļuvi mikrofonam sadaļā Iestatījumi."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Atvērt iestatījumus"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Cita ierīce"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Pārskata pārslēgšana"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Jūs netraucēs skaņas un vibrācija, izņemot signālus, atgādinājumus, pasākumus un zvanītājus, ko būsiet norādījis. Jūs joprojām dzirdēsiet atskaņošanai izvēlētos vienumus, tostarp mūziku, videoklipus un spēles."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Turpināt"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Lietotnes kopīgošana vai ierakstīšana"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vai izslēgt mobilos datus?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Izmantojot mobilo sakaru operatora <xliff:g id="CARRIER">%s</xliff:g> pakalpojumus, nevarēsiet piekļūt datiem vai internetam. Internetam varēsiet piekļūt, tikai izmantojot Wi-Fi savienojumu."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jūsu mobilo sakaru operators"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vai pārslēgties atpakaļ uz operatoru <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilie dati netiks automātiski pārslēgti, pamatojoties uz pieejamību."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nē, paldies"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Jā, pārslēgties"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Lietotne Iestatījumi nevar verificēt jūsu atbildi, jo cita lietotne aizsedz atļaujas pieprasījumu."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vai atļaut lietotnei <xliff:g id="APP_0">%1$s</xliff:g> rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Var lasīt informāciju no lietotnes <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Lupas loga iestatījumi"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atveriet pieejamības funkcijas. Pielāgojiet vai aizstājiet šo pogu iestatījumos.\n\n"<annotation id="link">"Skatīt iestatījumus"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Lai īslaicīgi paslēptu pogu, pārvietojiet to uz malu"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Atsaukt"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Noņemta saīsne “{label}”}zero{Noņemtas # saīsnes}one{Noņemta # saīsne}other{Noņemtas # saīsnes}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pārvietot augšpusē pa kreisi"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pārvietot augšpusē pa labi"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pārvietot apakšpusē pa kreisi"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pārvietot apakšpusē pa labi"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pārvietot uz malu un paslēpt"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pārvietot no malas un parādīt"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Noņemt"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"pārslēgt"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Ierīču vadīklas"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Izvēlieties lietotni, lai pievienotu vadīklas"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilie dati"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ir izveidots savienojums"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Īslaicīgi izveidots savienojums"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Vājš savienojums"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilo datu savienojums netiks veidots automātiski"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nav savienojuma"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nav pieejams neviens cits tīkls"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index e714055..0ff5545 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Не се препознава ликот"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користи отпечаток"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е поврзан."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентот на батеријата е непознат."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Поврзано со <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Управувајте со корисниците"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затвори"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Поврзано"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофонот е достапен"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камерата е достапна"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофонот и камерата се достапни"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофонот е вклучен"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофонот е исклучен"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофонот е овозможен за сите апликации и услуги."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Пристапот до микрофонот е оневозможен за сите апликации и услуги. Може да овозможите пристап до микрофонот во „Поставки > Приватност > Микрофон“."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Пристапот до микрофонот е оневозможен за сите апликации и услуги. Ова може да го промените во „Поставки > Приватност > Микрофон“."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камерата е вклучена"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камерата е исклучена"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камерата е овозможена за сите апликации и услуги."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Пристапот до камерата е оневозможен за сите апликации и услуги."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"За да го користите копчето за микрофон, овозможете пристап до микрофонот во „Поставки“."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Отворете ги поставките."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Друг уред"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Вклучи/исклучи преглед"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Нема да ве вознемируваат звуци и вибрации, освен од аларми, потсетници, настани и повикувачи што ќе ги наведете. Сѐ уште ќе слушате сѐ што ќе изберете да пуштите, како музика, видеа и игри."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Кога споделувате, снимате или емитувате апликација, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продолжи"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделете или снимете апликација"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се исклучи мобилниот интернет?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Нема да имате пристап до податоците или интернетот преку <xliff:g id="CARRIER">%s</xliff:g>. Интернетот ќе биде достапен само преку Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"вашиот оператор"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Да се префрли на <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобилниот интернет нема автоматски да се префрли според достапноста"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, фала"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, префрли се"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Бидејќи апликацијата го прикрива барањето за дозвола, „Поставките“ не може да го потврдат вашиот одговор."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Да се дозволи <xliff:g id="APP_0">%1$s</xliff:g> да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Може да чита информации од <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Поставки за прозорец за лупа"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Допрете за функциите за пристапност. Приспособете или заменете го копчево во „Поставки“.\n\n"<annotation id="link">"Прикажи поставки"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете го копчето до работ за да го сокриете привремено"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Врати"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Отстранета е {label} кратенка}one{Отстранети се # кратенка}other{Отстранети се # кратенки}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Премести горе десно"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Премести долу лево"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Премести долу десно"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Премести до работ и сокриј"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Премести над работ и прикажи"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Отстрани"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"вклучување/исклучување"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Контроли за уредите"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Изберете апликација за да додадете контроли"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилен интернет"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Поврзано"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Привремено поврзано"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слаба интернет-врска"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилниот интернет не може да се поврзе автоматски"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Нема интернет-врска"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нема други достапни мрежи"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 666e601..e8fdef35 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"മുഖം തിരിച്ചറിയാനാകുന്നില്ല"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ബ്ലൂടൂത്ത് കണക്റ്റുചെയ്തു."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ബാറ്ററി ശതമാനം അജ്ഞാതമാണ്."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"മൈക്രോഫോൺ ലഭ്യമാണ്"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ക്യാമറ ലഭ്യമാണ്"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"മൈക്രോഫോണും ക്യാമറയും ലഭ്യമാണ്"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"മൈക്രോഫോൺ ഓണാക്കി"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"മൈക്രോഫോൺ ഓഫാക്കി"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും മൈക്രോഫോൺ പ്രവർത്തനക്ഷമമാക്കിയിരിക്കുന്നു."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു. ക്രമീകരണം > സ്വകാര്യത > മൈക്രോഫോൺ എന്നതിൽ നിങ്ങൾക്ക് മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനക്ഷമമാക്കാം."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു. ക്രമീകരണം > സ്വകാര്യത > മൈക്രോഫോൺ എന്നതിൽ ഇത് നിങ്ങൾക്ക് മാറ്റാൻ കഴിയും."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ക്യാമറ ഓണാക്കി"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ക്യാമറ ഓഫാക്കി"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും ക്യാമറ പ്രവർത്തനക്ഷമമാക്കിയിരിക്കുന്നു."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും ക്യാമറാ ആക്സസ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"മൈക്രോഫോൺ ബട്ടൺ ഉപയോഗിക്കുന്നതിന്, ക്രമീകരണത്തിൽ മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനക്ഷമമാക്കുക."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ക്രമീകരണം തുറക്കുക."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"മറ്റ് ഉപകരണം"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"അവലോകനം മാറ്റുക"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"നിങ്ങൾ സജ്ജീകരിച്ച അലാറങ്ങൾ, റിമൈൻഡറുകൾ, ഇവന്റുകൾ, കോളർമാർ എന്നിവയിൽ നിന്നുള്ള ശബ്ദങ്ങളും വൈബ്രേഷനുകളുമൊഴികെ മറ്റൊന്നും നിങ്ങളെ ശല്യപ്പെടുത്തുകയില്ല. സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയുൾപ്പെടെ പ്ലേ ചെയ്യുന്നതെന്തും നിങ്ങൾക്ക് തുടർന്നും കേൾക്കാൻ കഴിയും."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"തുടരുക"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ഒരു ആപ്പ് പങ്കിടുക അല്ലെങ്കിൽ റെക്കോർഡ് ചെയ്യുക"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്ക്കുക"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"മൊബൈൽ ഡാറ്റ ഓഫാക്കണോ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"നിങ്ങൾക്ക് ഡാറ്റയിലേക്ക് ആക്സസോ അല്ലെങ്കിൽ <xliff:g id="CARRIER">%s</xliff:g> മുഖേനയുള്ള ഇന്റർനെറ്റോ ഉണ്ടാകില്ല. വൈഫൈ മുഖേന മാത്രമായിരിക്കും ഇന്റർനെറ്റ് ലഭ്യത."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"നിങ്ങളുടെ കാരിയർ"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> എന്നതിലേക്ക് വീണ്ടും മാറണോ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ലഭ്യതയുടെ അടിസ്ഥാനത്തിൽ, മൊബൈൽ ഡാറ്റ സ്വയമേവ മാറില്ല"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"വേണ്ട"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ഉവ്വ്, മാറുക"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"അനുമതി അഭ്യർത്ഥനയെ ഒരു ആപ്പ് മറയ്ക്കുന്നതിനാൽ, ക്രമീകരണത്തിന് നിങ്ങളുടെ പ്രതികരണം പരിശോധിച്ചുറപ്പിക്കാനാകില്ല."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-ൽ നിന്ന് വിവരങ്ങൾ വായിക്കാനാകും"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"മാഗ്നിഫയർ വിൻഡോ ക്രമീകരണം"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ഉപയോഗസഹായി ഫീച്ചർ തുറക്കാൻ ടാപ്പ് ചെയ്യൂ. ക്രമീകരണത്തിൽ ഈ ബട്ടൺ ഇഷ്ടാനുസൃതമാക്കാം, മാറ്റാം.\n\n"<annotation id="link">"ക്രമീകരണം കാണൂ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"തൽക്കാലം മറയ്ക്കുന്നതിന് ബട്ടൺ അരുകിലേക്ക് നീക്കുക"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"പഴയപടിയാക്കുക"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} കുറുക്കുവഴി നീക്കം ചെയ്തു}other{# കുറുക്കുവഴികൾ നീക്കം ചെയ്തു}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"മുകളിൽ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"മുകളിൽ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ചുവടെ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"എഡ്ജിലേക്ക് നീക്കി മറയ്ക്കുക"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"എഡ്ജിൽ നിന്ന് നീക്കി കാണിക്കൂ"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"നീക്കം ചെയ്യുക"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"മാറ്റുക"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ഉപകരണ നിയന്ത്രണങ്ങൾ"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"നിയന്ത്രണങ്ങൾ ചേർക്കാൻ ആപ്പ് തിരഞ്ഞെടുക്കുക"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"മൊബൈൽ ഡാറ്റ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"കണക്റ്റ് ചെയ്തു"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"താൽക്കാലികമായി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ദുർബലമായ കണക്ഷൻ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"മൊബൈൽ ഡാറ്റ സ്വയം കണക്റ്റ് ചെയ്യില്ല"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"കണക്ഷനില്ല"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"മറ്റ് നെറ്റ്വർക്കുകളൊന്നും ലഭ്യമല്ല"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 769cdda..6be28dd 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Царайг танихгүй байна"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Оронд нь хурууны хээ ашиглах"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth холбогдсон."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарейн хувь тодорхойгүй байна."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>-тай холбогдсон."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон боломжтой байна"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камер боломжтой байна"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон болон камер боломжтой байна"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофоныг асаасан"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофоныг унтраасан"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофоныг бүх програм, үйлчилгээнд идэвхжүүлсэн."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Бүх програм, үйлчилгээнд микрофоны хандалтыг идэвхгүй болгосон. Та микрофоны хандалтыг тохиргоо идэвхжүүлж байна > Нууцлал > Микрофон идэвхжүүлж болно."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Бүх програм, үйлчилгээнд микрофоны хандалтыг идэвхгүй болгосон. Та үүнийг Тохиргоо > Нууцлал > Микрофон идэвхжүүлж болно."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камерыг асаасан"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камерыг унтраасан"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камерыг бүх програм, үйлчилгээнд идэвхжүүлсэн."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Бүх апп болон үйлчилгээнд камерын хандалтыг идэвхгүй болгосон."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Микрофоны товчлуурыг ашиглахын тулд \"Тохиргоо\" хэсэгт микрофоны хандалтыг идэвхжүүлнэ үү."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Тохиргоог нээнэ үү."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Бусад төхөөрөмж"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Тоймыг асаах/унтраах"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Танд сэрүүлэг, сануулга, арга хэмжээ, таны сонгосон дуудлага илгээгчээс бусад дуу, чичиргээ саад болохгүй. Та хөгжим, видео, тоглоом зэрэг тоглуулахыг хүссэн бүх зүйлээ сонсох боломжтой хэвээр байна."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> нь тухайн апп дээр харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Үргэлжлүүлэх"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Хуваалцах эсвэл бичих апп"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Түүх"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобайл датаг унтраах уу?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Та <xliff:g id="CARRIER">%s</xliff:g>-р дата эсвэл интернэтэд хандах боломжгүй болно. Интернэтэд зөвхөн Wi-Fi-р холбогдох боломжтой болно."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"таны оператор компани"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> руу буцаан сэлгэх үү?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобайл дата нь боломжтой эсэхэд тулгуурлан автоматаар сэлгэхгүй"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Үгүй, баярлалаа"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Тийм, сэлгэе"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Апп нь зөвшөөрлийн хүсэлтийг танихгүй байгаа тул Тохиргооноос таны хариултыг баталгаажуулах боломжгүй байна."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>-д <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг (slices) харуулахыг зөвшөөрөх үү?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Энэ нь <xliff:g id="APP">%1$s</xliff:g>-с мэдээлэл унших боломжтой"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Томруулагчийн цонхны тохиргоо"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Хандалтын онцлогуудыг нээхийн тулд товшино уу. Энэ товчлуурыг Тохиргоо хэсэгт өөрчилж эсвэл солиорой.\n\n"<annotation id="link">"Тохиргоог харах"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Үүнийг түр нуухын тулд товчлуурыг зах руу зөөнө үү"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Болих"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} товчлолыг хассан}other{# товчлолыг хассан}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Зүүн дээш зөөх"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Баруун дээш зөөх"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Зүүн доош зөөх"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Баруун доош зөөх"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ирмэг рүү зөөж, нуух"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Ирмэгээс гаргаж, харуулах"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Хасах"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"асаах/унтраах"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Төхөөрөмжийн хяналт"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Хяналтууд нэмэхийн тулд аппыг сонгоно уу"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобайл дата"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Холбогдсон"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Түр зуур холбогдсон"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Холболт сул байна"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобайл дата автоматаар холбогдохгүй"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Холболт алга"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Өөр боломжтой сүлжээ байхгүй байна"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index d45b1e5..7c1dcc3 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरा ओळखू शकत नाही"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"त्याऐवजी फिंगरप्रिंट वापरा"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट केले."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"बॅटरीच्या चार्जिंगची टक्केवारी माहित नाही."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> शी कनेक्ट केले."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"वापरकर्ते व्यवस्थापित करा"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"पूर्ण झाले"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"बंद करा"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"कनेक्ट केलेले"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"मायक्रोफोन उपलब्ध आहे"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"कॅमेरा उपलब्ध आहे"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"मायक्रोफोन आणि कॅमेरा या गोष्टी उपलब्ध आहेत"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"मायक्रोफोन सुरू केला"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"मायक्रोफोन बंद केला"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"सर्व ॲप्स आणि सेवांसाठी मायक्रोफोन सुरू केला आहे."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"सर्व ॲप्स आणि सेवांसाठी मायक्रोफोन अॅक्सेस बंद केला आहे. तुम्ही मायक्रोफोन अॅक्सेस सेटिंग्ज > गोपनीयता > मायक्रोफोन मधून सुरू करू शकता."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"सर्व ॲप्स आणि सेवांसाठी मायक्रोफोन अॅक्सेस बंद केला आहे. तुम्ही हे सेटिंग्ज > गोपनीयता > मायक्रोफोन मधून बदलू शकता ."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"कॅमेरा सुरू केला आहे"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"कॅमेरा बंद केला आहे"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"सर्व ॲप्स आणि सेवांसाठी कॅमेरा सुरू केला आहे."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"सर्व ॲप्स आणि सेवांसाठी कॅमेरा अॅक्सेस बंद केला आहे."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"मायक्रोफोन बटण वापरण्यासाठी, सेटिंग्जमधून मायक्रोफोन अॅक्सेस सुरू करा."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"सेटिंग्ज उघडा."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"इतर डिव्हाइस"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"अवलोकन टॉगल करा."</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"अलार्म, रिमाइंडर, इव्हेंट आणि तुम्ही निश्चित केलेल्या कॉलर व्यतिरिक्त तुम्हाला कोणत्याही आवाज आणि कंपनांचा व्यत्त्यय आणला जाणार नाही. तरीही तुम्ही प्ले करायचे ठरवलेले कोणतेही संगीत, व्हिडिओ आणि गेमचे आवाज ऐकू शकतात."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तुम्ही अॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"पुढे सुरू ठेवा"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"अॅप शेअर किंवा रेकॉर्ड करा"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करायचा?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"तुम्हाला <xliff:g id="CARRIER">%s</xliff:g> मधून डेटा किंवा इंटरनेटचा अॅक्सेस नसेल. इंटरनेट फक्त वाय-फाय मार्फत उपलब्ध असेल."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तुमचा वाहक"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> वर परत स्विच करायचे आहे का?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"उपलब्धतेच्या आधारावर मोबाइल डेटा आपोआप स्विच होणार नाही"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"नाही, नको"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"होय, स्विच करा"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"अॅप परवानगी विनंती अस्पष्ट करत असल्याने, सेटिंग्ज तुमचा प्रतिसाद पडताळू शकत नाहीत."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवण्याची अनुमती द्यायची का?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ते <xliff:g id="APP">%1$s</xliff:g> ची माहिती वाचू शकते"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"मॅग्निफायर विंडो सेटिंग्ज"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"अॅक्सेसिबिलिटी वैशिष्ट्ये उघडण्यासाठी, टॅप करा. सेटिंग्जमध्ये हे बटण कस्टमाइझ करा किंवा बदला.\n\n"<annotation id="link">"सेटिंग्ज पहा"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटण तात्पुरते लपवण्यासाठी ते कोपर्यामध्ये हलवा"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहिल्यासारखे करा"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} शॉर्टकट काढून टाकला आहे}other{# शॉर्टकट काढून टाकले आहेत}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"वर डावीकडे हलवा"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"वर उजवीकडे हलवा"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"तळाशी डावीकडे हलवा"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"तळाशी उजवीकडे हलवा"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"एजवर हलवा आणि लपवा"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"एजवर हलवा आणि दाखवा"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"काढून टाका"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टॉगल करा"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"डिव्हाइस नियंत्रणे"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"नियंत्रणे जोडण्यासाठी ॲप निवडा"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट केले आहे"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"तात्पुरते कनेक्ट केलेले"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"खराब कनेक्शन"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा ऑटो-कनेक्ट होणार नाही"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"कोणतेही कनेक्शन नाही"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"इतर कोणतेही नेटवर्क उपलब्ध नाहीत"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 9e43396..08132f4 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Tak dapat mengecam wajah"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan cap jari"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth disambungkan."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Peratusan kuasa bateri tidak diketahui."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Disambungkan kepada <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Urus pengguna"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tutup"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Disambungkan"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon tersedia"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera tersedia"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon dan kamera tersedia"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Peranti lain"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Togol Ikhtisar"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Anda tidak akan diganggu oleh bunyi dan getaran, kecuali daripada penggera, peringatan, acara dan pemanggil yang anda tetapkan. Anda masih mendengar item lain yang anda pilih untuk dimainkan termasuk muzik, video dan permainan."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Apabila anda berkongsi, merakam atau menghantar apl, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> mempunyai akses kepada apa-apa yang dipaparkan atau dimainkan pada apl tersebut. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Teruskan"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Kongsi atau rakam apl"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Urus"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Sejarah"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Matikan data mudah alih?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan mempunyai akses kepada data atau Internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya tersedia melaui Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"pembawa anda"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Tukar kembali kepada <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data mudah alih tidak akan ditukar secara automatik berdasarkan ketersediaan"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Tidak perlu"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ya, tukar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Oleh sebab apl melindungi permintaan kebenaran, Tetapan tidak dapat mengesahkan jawapan anda."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Benarkan <xliff:g id="APP_0">%1$s</xliff:g> menunjukkan <xliff:g id="APP_2">%2$s</xliff:g> hirisan?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Hos hirisan boleh membaca maklumat daripada <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Tetapan tetingkap penggadang"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketik untuk membuka ciri kebolehaksesan. Sesuaikan/gantikan butang ini dalam Tetapan.\n\n"<annotation id="link">"Lihat tetapan"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Gerakkan butang ke tepi untuk disembunyikan buat sementara waktu"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Buat asal"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} pintasan dialih keluar}other{# pintasan dialih keluar}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Alihkan ke atas sebelah kiri"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Alihkan ke atas sebelah kanan"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Alihkan ke bawah sebelah kiri"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Alihkan ke bawah sebelah kanan"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Alihkan ke tepi dan sorokkan"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Alihkan ke tepi dan tunjukkan"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Alih keluar"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"togol"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kawalan peranti"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambahkan kawalan"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Data mudah alih"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Disambungkan"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Disambungkan buat sementara waktu"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Sambungan lemah"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Data mudah alih tidak akan autosambung"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Tiada sambungan"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Tiada rangkaian lain yang tersedia"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 8f404b8..51dbb5d 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"မျက်နှာကို မမှတ်မိပါ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"လက်ဗွေကို အစားထိုးသုံးပါ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ဘလူးတုသ်ချိတ်ဆက်ထားမှု"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ဘက်ထရီရာခိုင်နှုန်းကို မသိပါ။"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>သို့ ချိတ်ဆက်ထား"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"အသုံးပြုသူများ စီမံရန်"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ပြီးပါပြီ"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ပိတ်ရန်"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"ချိတ်ဆက်ထား"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"မိုက်ခရိုဖုန်း သုံးနိုင်သည်"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ကင်မရာ သုံးနိုင်သည်"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"မိုက်ခရိုဖုန်းနှင့် ကင်မရာ သုံးနိုင်သည်"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"မိုက်ခရိုဖုန်းကို ဖွင့်ထားသည်"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် မိုက်ခရိုဖုန်းကို ဖွင့်ထားသည်။"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် မိုက်ခရိုဖုန်းသုံးခွင့်ကို ပိတ်ထားသည်။ မိုက်ခရိုဖုန်းသုံးခွင့်ကို ဆက်တင်များ > ကိုယ်ရေးအချက်အလက်လုံခြုံမှု > မိုက်ခရိုဖုန်း တွင်ဖွင့်နိုင်သည်။"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် မိုက်ခရိုဖုန်းသုံးခွင့်ကို ပိတ်ထားသည်။ ၎င်းကို ဆက်တင်များ > ကိုယ်ရေးအချက်အလက်လုံခြုံမှု > မိုက်ခရိုဖုန်း တွင်ပြောင်းနိုင်သည်။"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ကင်မရာ ဖွင့်ထားသည်"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ကင်မရာ ပိတ်ထားသည်"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် ကင်မရာကို ဖွင့်ထားသည်။"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် ကင်မရာသုံးခွင့်ကို ပိတ်ထားသည်။"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"မိုက်ခရိုဖုန်းခလုတ် အသုံးပြုရန် ဆက်တင်များတွင် မိုက်ခရိုဖုန်းသုံးခွင့်ကို ဖွင့်ပါ။"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ဆက်တင်များဖွင့်ရန်။"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"အခြားစက်ပစ္စည်း"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ဖွင့်၊ ပိတ် အနှစ်ချုပ်"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"နှိုးစက်သံ၊ သတိပေးချက်အသံများ၊ ပွဲစဉ်သတိပေးသံများနှင့် သင်ခွင့်ပြုထားသူများထံမှ ဖုန်းခေါ်မှုများမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ရှေ့ဆက်ရန်"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"အက်ပ် မျှဝေခြင်း (သို့) ရိုက်ကူးခြင်း"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"မိုဘိုင်းဒေတာ ပိတ်မလား။"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> မှတစ်ဆင့် ဒေတာ သို့မဟုတ် အင်တာနက်ကို မသုံးနိုင်ပါ။ Wi-Fi ဖြင့်သာ အင်တာနက် သုံးနိုင်သည်။"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"သင်၏ ဝန်ဆောင်မှုပေးသူ"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> သို့ ပြန်ပြောင်းမလား။"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ရနိုင်မှုပေါ် အခြေခံပြီး မိုဘိုင်းဒေတာကို အလိုအလျောက် မပြောင်းပါ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"မလိုပါ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ပြောင်းရန်"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"အပလီကေးရှင်းတစ်ခုက ခွင့်ပြုချက်တောင်းခံမှုကို ပိတ်ထားသောကြောင့် ဆက်တင်များသည် သင်၏ လုပ်ဆောင်ကို တုံ့ပြန်နိုင်ခြင်းမရှိပါ။"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> အား <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များ ပြသခွင့်ပြုပါသလား။"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> မှ အချက်အလက်ကို ဖတ်နိုင်သည်"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"မှန်ဘီလူးဝင်းဒိုး ဆက်တင်များ"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ရန် တို့ပါ။ ဆက်တင်များတွင် ဤခလုတ်ကို စိတ်ကြိုက်ပြင်ပါ (သို့) လဲပါ။\n\n"<annotation id="link">"ဆက်တင်များ ကြည့်ရန်"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ခလုတ်ကို ယာယီဝှက်ရန် အစွန်းသို့ရွှေ့ပါ"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"နောက်ပြန်ရန်"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{ဖြတ်လမ်းလင့်ခ် {label} ခု ဖယ်ရှားပြီးပြီ}other{ဖြတ်လမ်းလင့်ခ် # ခု ဖယ်ရှားပြီးပြီ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ဘယ်ဘက်ထိပ်သို့ ရွှေ့ရန်"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ညာဘက်ထိပ်သို့ ရွှေ့ရန်"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ဘယ်ဘက်အောက်ခြေသို့ ရွှေ့ရန်"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ညာဘက်အောက်ခြေသို့ ရွှေ့ရန်"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"အစွန်းသို့ရွှေ့ပြီး ဝှက်ရန်"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"အစွန်းမှရွှေ့ပြီး ပြရန်"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ဖယ်ရှားရန်"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ပြောင်းရန်"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"စက်ထိန်းစနစ်"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"ထိန်းချုပ်မှုများထည့်ရန် အက်ပ်ရွေးခြင်း"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"မိုဘိုင်းဒေတာ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ချိတ်ဆက်ထားသည်"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ယာယီချိတ်ဆက်ထားသည်"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ချိတ်ဆက်မှုအားနည်းသည်"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"မိုဘိုင်းဒေတာ အော်တိုမချိတ်ပါ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ချိတ်ဆက်မှုမရှိပါ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"အခြားကွန်ရက်များ မရှိပါ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 94b8e90..eba9731 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet gjenkjennes ikke"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bruk fingeravtrykk"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth er tilkoblet."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriprosenten er ukjent."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Koblet til <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrer brukere"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Ferdig"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Lukk"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Tilkoblet"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon er tilgjengelig"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera er tilgjengelig"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon og kamera er tilgjengelig"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonen er slått på"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonen er slått av"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonen er slått på for alle apper og tjenester."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofontilgang er slått av for alle apper og tjenester. Du kan gi mikrofontilgang i Innstillinger > Personvern > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofontilgang er slått av for alle apper og tjenester. Du kan endre dette i Innstillinger > Personvern > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kameraet er slått på"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kameraet er slått av"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameraet er slått på for alle apper og tjenester."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kameratilgang er slått av for alle apper og tjenester."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"For å bruke mikrofonknappen, gi mikrofontilgang i innstillingene."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Åpne innstillingene."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Annen enhet"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Slå oversikten av eller på"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Du blir ikke forstyrret av lyder og vibrasjoner, med unntak av alarmer, påminnelser, aktiviteter og oppringere du angir. Du kan fremdeles høre alt du velger å spille av, for eksempel musikk, videoer og spill."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, tar opp eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsett"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller ta opp en app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du slå av mobildata?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du får ikke tilgang til data eller internett via <xliff:g id="CARRIER">%s</xliff:g>. Internett er bare tilgjengelig via Wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatøren din"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vil du bytte tilbake til <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Det byttes ikke mobildataoperatør automatisk basert på tilgjengelighet"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nei takk"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, bytt"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Fordi en app skjuler tillatelsesforespørselen, kan ikke Innstillinger bekrefte svaret ditt."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Den kan lese informasjon fra <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Innstillinger for forstørringsvindu"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trykk for å åpne tilgj.funksjoner. Tilpass eller bytt knappen i Innstillinger.\n\n"<annotation id="link">"Se innstillingene"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytt knappen til kanten for å skjule den midlertidig"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Angre"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} snarvei er fjernet}other{# snarveier er fjernet}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytt til øverst til venstre"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flytt til øverst til høyre"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flytt til nederst til venstre"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flytt til nederst til høyre"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flytt til kanten og skjul"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flytt ut kanten og vis"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Fjern"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"slå av/på"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyring"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Velg en app for å legge til kontroller"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Tilkoblet"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Koblet til midlertidig"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dårlig forbindelse"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobildata kobler ikke til automatisk"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen tilkobling"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ingen andre nettverk er tilgjengelige"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 35937a9..23ff09b 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"अनुहार पहिचान गर्न सकिएन"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ब्याट्रीमा कति प्रतिशत चार्ज छ भन्ने कुराको जानाकरी छैन।"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> मा जडित।"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"प्रयोगकर्ताहरू व्यवस्थित गर्नुहोस्"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"भयो"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"बन्द गर्नुहोस्"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"जोडिएको"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"माइक्रोफोन उपलब्ध छ"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"क्यामेरा उपलब्ध छ"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"माइक्रोफोन र क्यामेरा उपलब्ध छन्"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"माइक्रोफोन अन गरियो"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"माइक्रोफोन अफ गरियो"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"सबै एप तथा सेवाहरूलाई माइक्रोफोन प्रयोग गर्ने अनुमति दिइएको छ।"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"कुनै पनि एप तथा सेवालाई माइक्रोफोन प्रयोग गर्ने अनुमति दिइएको छैन। तपाईं \"सेटिङ > गोपनीयता > माइक्रोफोन\" मा गई माइक्रोफोन प्रयोग गर्ने अनुमति दिन सक्नुहुन्छ।"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"कुनै पनि एप तथा सेवालाई माइक्रोफोन प्रयोग गर्ने अनुमति दिइएको छैन। तपाईं \"सेटिङ > गोपनीयता > माइक्रोफोन\" मा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"क्यामेरा अन गरियो"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"क्यामेरा अफ गरियो"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"सबै एप तथा सेवाहरूलाई क्यामेरा प्रयोग गर्ने अनुमति दिइएको छ।"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"कुनै पनि एप तथा सेवालाई क्यामेरा प्रयोग गर्ने अनुमति दिइएको छैन।"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"तपाईं माइक्रोफोन बटन प्रयोग गर्न चाहनुहुन्छ भने सेटिङमा गई माइक्रोफोन प्रयोग गर्ने अनुमति दिनुहोस्।"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"सेटिङ खोल्नुहोस्।"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"अर्को डिभाइड"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"परिदृश्य टगल गर्नुहोस्"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"तपाईंलाई अलार्म, रिमाइन्डर, कार्यक्रम र तपाईंले निर्दिष्ट गर्नुभएका कलरहरू बाहेकका ध्वनि र कम्पनहरूले बाधा पुऱ्याउने छैनन्। तपाईंले अझै सङ्गीत, भिडियो र खेलहरू लगायत आफूले प्ले गर्न छनौट गरेका जुनसुकै कुरा सुन्न सक्नुहुनेछ।"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा निष्क्रिय पार्ने हो?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"तपाईं <xliff:g id="CARRIER">%s</xliff:g> मार्फत डेटा वा इन्टरनेट प्रयोग गर्न सक्नुहुने छैन। Wi-Fi मार्फत मात्र इन्टरनेट उपलब्ध हुने छ।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तपाईंको सेवा प्रदायक"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"फेरि <xliff:g id="CARRIER">%s</xliff:g> को मोबाइल डेटा अन गर्ने हो?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"मोबाइल डेटा उपलब्धताका आधारमा स्वतः बदलिँदैन"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"पर्दैन, धन्यवाद"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"हुन्छ, बदल्नुहोस्"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"कुनै एपको कारणले अनुमतिसम्बन्धी अनुरोध बुझ्न गाह्रो भइरहेकोले सेटिङहरूले तपाईंको प्रतिक्रिया प्रमाणित गर्न सक्दैनन्।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> लाई <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन अनुमति दिने हो?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- यसले <xliff:g id="APP">%1$s</xliff:g> बाट जानकारी पढ्न सक्छ"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"म्याग्निफायर विन्डोसम्बन्धी सेटिङ"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सर्वसुलभता कायम गर्ने सुविधा खोल्न ट्याप गर्नुहोस्। सेटिङमा गई यो बटन कस्टमाइज गर्नुहोस् वा बदल्नुहोस्।\n\n"<annotation id="link">"सेटिङ हेर्नुहोस्"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"यो बटन केही बेर नदेखिने पार्न किनारातिर सार्नुहोस्"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"अन्डू गर्नुहोस्"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} वटा सर्टकट हटाइयो}other{# वटा सर्टकट हटाइयो}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सिरानको बायाँतिर सार्नुहोस्"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"सिरानको दायाँतिर सार्नुहोस्"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"पुछारको बायाँतिर सार्नुहोस्"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"पुछारको दायाँतिर सार्नुहोस्"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"किनारामा सार्नुहोस् र नदेखिने पार्नु…"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"किनाराबाट सार्नुहोस् र देखिने पार्नु…"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"हटाउनुहोस्"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टगल गर्नुहोस्"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"डिभाइस नियन्त्रण गर्ने विजेटहरू"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"कन्ट्रोल थप्नु पर्ने एप छान्नुहोस्"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"इन्टरनेटमा कनेक्ट गरिएको छ"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"केही समयका लागि मोबाइल डेटामा कनेक्ट गरिएको छ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"इन्टरनेट राम्री चलेको छैन"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा स्वतः कनेक्ट हुँदैन"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"इन्टरनेट छैन"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"अन्य नेटवर्क उपलब्ध छैनन्"</string>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 6f87169..99bc794 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -24,11 +24,6 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="TextAppearance.QS.Status" parent="TextAppearance.QS.TileLabel.Secondary">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
<!-- Screenshots -->
<style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:windowNoTitle">true</item>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 4de0331..a36f811 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Gezicht niet herkend"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Vingerafdruk gebruiken"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-verbinding ingesteld."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterijpercentage onbekend."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Verbonden met <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gebruikers beheren"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sluiten"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Verbonden"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfoon beschikbaar"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera beschikbaar"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfoon en camera beschikbaar"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfoon staat aan"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfoon staat uit"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microfoon staat aan voor alle apps en services."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microfoontoegang staat uit voor alle apps en services. Je kunt microfoontoegang aanzetten via Instellingen > Privacy > Microfoon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microfoontoegang staat uit voor alle apps en services. Je kunt dit wijzigen via Instellingen > Privacy > Microfoon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera staat aan"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera staat uit"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera staat aan voor alle apps en services."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Cameratoegang staat uit voor alle apps en services."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Als je de microfoonknop wilt gebruiken, zet je microfoontoegang aan via Instellingen."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Instellingen openen."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Ander apparaat"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Overzicht aan- of uitzetten"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Je wordt niet gestoord door geluiden en trillingen, behalve bij wekkers, herinneringen, afspraken en specifieke bellers die je selecteert. Je kunt nog steeds alles horen wat je wilt afspelen, waaronder muziek, video\'s en games."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Als je deelt, opneemt of cast, heeft <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Doorgaan"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App delen of opnemen"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geschiedenis"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobiele data uitzetten?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Je hebt dan geen toegang meer tot data of internet via <xliff:g id="CARRIER">%s</xliff:g>. Internet is alleen nog beschikbaar via wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"je provider"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Terugschakelen naar <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiele data worden niet automatisch overgezet op basis van beschikbaarheid"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nee, bedankt"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, overschakelen"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Aangezien een app een rechtenverzoek afdekt, kan Instellingen je reactie niet verifiëren."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> toestaan om segmenten van <xliff:g id="APP_2">%2$s</xliff:g> te tonen?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Deze kan informatie lezen van <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Instellingen voor vergrotingsvenster"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik voor toegankelijkheidsfuncties. Wijzig of vervang deze knop via Instellingen.\n\n"<annotation id="link">"Naar Instellingen"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Knop naar de rand verplaatsen om deze tijdelijk te verbergen"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ongedaan maken"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Snelkoppeling voor {label} verwijderd}other{# snelkoppelingen verwijderd}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Naar linksboven verplaatsen"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Naar rechtsboven verplaatsen"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Naar linksonder verplaatsen"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Naar rechtsonder verplaatsen"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Naar rand verplaatsen en verbergen"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Over rand verplaatsen en tonen"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Verwijderen"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"schakelen"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Apparaatbediening"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Kies de app waaraan je bedieningselementen wilt toevoegen"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Verbonden"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tijdelijk verbonden"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Matige verbinding"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data maakt niet automatisch verbinding"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen andere netwerken beschikbaar"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 16941b8..fe57b75 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ଫେସ ଚିହ୍ନଟ ହୋଇପାରିବ ନାହିଁ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ବ୍ଲୁଟୂଥ୍ ସଂଯୋଗ କରାଯାଇଛି।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ବ୍ୟାଟେରୀ ଶତକଡ଼ା ଅଜଣା ଅଟେ।"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ସହ ସଂଯୁକ୍ତ"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ୟୁଜରମାନଙ୍କୁ ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ହୋଇଗଲା"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"ସଂଯୁକ୍ତ"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ମାଇକ୍ରୋଫୋନ ଉପଲବ୍ଧ"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"କ୍ୟାମେରା ଉପଲବ୍ଧ"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ମାଇକ୍ରୋଫୋନ ଏବଂ କ୍ୟାମେରା ଉପଲବ୍ଧ"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"ଅନ୍ୟ ଡିଭାଇସ୍"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ସଂକ୍ଷିପ୍ତ ବିବରଣୀକୁ ଟୋଗଲ୍ କରନ୍ତୁ"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"ଆଲାର୍ମ, ରିମାଇଣ୍ଡର୍, ଇଭେଣ୍ଟ ଏବଂ ଆପଣ ନିର୍ଦ୍ଦିଷ୍ଟ କରିଥିବା କଲର୍ଙ୍କ ବ୍ୟତୀତ ଆପଣଙ୍କ ଧ୍ୟାନ ଅନ୍ୟ କୌଣସି ଧ୍ୱନୀ ଏବଂ ଭାଇବ୍ରେଶନ୍ରେ ଆକର୍ଷଣ କରାଯିବନାହିଁ। ମ୍ୟୁଜିକ୍, ଭିଡିଓ ଏବଂ ଗେମ୍ ସମେତ ନିଜେ ଚଲାଇବାକୁ ବାଛିଥିବା ଅନ୍ୟ ସବୁକିଛି ଆପଣ ଶୁଣିପାରିବେ।"</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ସେହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ଜାରି ରଖନ୍ତୁ"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ଏକ ଆପକୁ ସେୟାର କିମ୍ବା ରେକର୍ଡ କରନ୍ତୁ"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ମୋବାଇଲ୍ ଡାଟା ବନ୍ଦ କରିବେ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ଡାଟା କିମ୍ବା ଇଣ୍ଟରନେଟ୍କୁ <xliff:g id="CARRIER">%s</xliff:g> ଦ୍ଵାରା ଆପଣଙ୍କର ଆକ୍ସେସ୍ ରହିବ ନାହିଁ। ଇଣ୍ଟରନେଟ୍ କେବଳ ୱାଇ-ଫାଇ ମାଧ୍ୟମରେ ଉପଲବ୍ଧ ହେବ।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ଆପଣଙ୍କ କେରିଅର୍"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>କୁ ପୁଣି ସ୍ୱିଚ କରିବେ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ଉପଲବ୍ଧତା ଆଧାରରେ ମୋବାଇଲ ଡାଟା ସ୍ୱଚାଳିତ ଭାବେ ସ୍ୱିଚ ହେବ ନାହିଁ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ନା, ଧନ୍ୟବାଦ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ହଁ, ସ୍ୱିଚ କରନ୍ତୁ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ଗୋଟିଏ ଆପ୍ ଏକ ଅନୁମତି ଅନୁରୋଧକୁ ଦେଖିବାରେ ବାଧା ଦେଉଥିବାରୁ, ସେଟିଙ୍ଗ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରିପାରିବ ନାହିଁ।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_0">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ଏହା <xliff:g id="APP">%1$s</xliff:g>ରୁ ସୂଚନାକୁ ପଢ଼ିପାରିବ"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ମ୍ୟାଗ୍ନିଫାୟର ୱିଣ୍ଡୋର ସେଟିଂସ"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚର ଖୋଲିବାକୁ ଟାପ କରନ୍ତୁ। ସେଟିଂସରେ ଏହି ବଟନକୁ କଷ୍ଟମାଇଜ କର କିମ୍ବା ବଦଳାଅ।\n\n"<annotation id="link">"ସେଟିଂସ ଦେଖନ୍ତୁ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ବଟନକୁ ଅସ୍ଥାୟୀ ଭାବେ ଲୁଚାଇବା ପାଇଁ ଏହାକୁ ଗୋଟିଏ ଧାରକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label}ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}other{#ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ଶୀର୍ଷ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ଶୀର୍ଷ ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ନିମ୍ନ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ନିମ୍ନ ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ଧାରକୁ ମୁଭ୍ କରି ଲୁଚାନ୍ତୁ"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ଧାର ବାହାରକୁ ମୁଭ୍ କରି ଦେଖାନ୍ତୁ"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ଟୋଗଲ୍ କରନ୍ତୁ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ଡିଭାଇସ୍ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଯୋଗ କରିବାକୁ ଆପ୍ ବାଛନ୍ତୁ"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ମୋବାଇଲ ଡାଟା"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ସଂଯୋଗ କରାଯାଇଛି"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ଅସ୍ଥାୟୀ ରୂପେ କନେକ୍ଟ କରାଯାଇଛି"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ଦୁର୍ବଳ କନେକ୍ସନ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ମୋବାଇଲ ଡାଟା ସ୍ୱତଃ-ସଂଯୋଗ ହେବ ନାହିଁ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ସଂଯୋଗ ନାହିଁ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ଅନ୍ୟ କୌଣସି ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index fce69ec..c119e8c 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ਕਨੈਕਟ ਕੀਤੀ।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ਬੈਟਰੀ ਪ੍ਰਤੀਸ਼ਤ ਅਗਿਆਤ ਹੈ।"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ।"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ਵਰਤੋਂਕਾਰਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ਹੋ ਗਿਆ"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ਬੰਦ ਕਰੋ"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"ਕਨੈਕਟ ਕੀਤਾ"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਹੈ"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ਕੈਮਰਾ ਉਪਲਬਧ ਹੈ"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਕੈਮਰਾ ਉਪਲਬਧ ਹੈ"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਚਾਲੂ ਹੈ।"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਬੰਦ ਹੈ। ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ > ਪਰਦੇਦਾਰੀ > ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਵਿੱਚ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਨੂੰ ਚਾਲੂ ਕਰ ਸਕਦੇ ਹੋ।"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਬੰਦ ਹੈ। ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ > ਪਰਦੇਦਾਰੀ > ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਵਿੱਚ ਇਸਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ਕੈਮਰਾ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ਕੈਮਰਾ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਕੈਮਰਾ ਚਾਲੂ ਹੈ।"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਕੈਮਰਾ ਪਹੁੰਚ ਬੰਦ ਹੈ।"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਟਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ, ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਚਾਲੂ ਕਰੋ।"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ।"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"ਹੋਰ ਡੀਵਾਈਸ"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ਰੂਪ-ਰੇਖਾ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਤੁਹਾਨੂੰ ਪਰੇਸ਼ਾਨ ਨਹੀਂ ਕਰਨਗੀਆਂ, ਸਿਵਾਏ ਅਲਾਰਮਾਂ, ਯਾਦ-ਦਹਾਨੀਆਂ, ਵਰਤਾਰਿਆਂ, ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਨਿਰਧਾਰਤ ਕੀਤੇ ਕਾਲਰਾਂ ਦੀ ਸੂਰਤ ਵਿੱਚ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ ਆਪਣੀ ਚੋਣ ਅਨੁਸਾਰ ਕੁਝ ਵੀ ਸੁਣ ਸਕਦੇ ਹੋ।"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ਜਾਰੀ ਰੱਖੋ"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ਕੀ ਮੋਬਾਈਲ ਡਾਟਾ ਬੰਦ ਕਰਨਾ ਹੈ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ਤੁਸੀਂ <xliff:g id="CARRIER">%s</xliff:g> ਰਾਹੀਂ ਡਾਟੇ ਜਾਂ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕਰ ਸਕੋਗੇ। ਇੰਟਰਨੈੱਟ ਸਿਰਫ਼ ਵਾਈ-ਫਾਈ ਰਾਹੀਂ ਉਪਲਬਧ ਹੋਵੇਗਾ।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ਤੁਹਾਡਾ ਕੈਰੀਅਰ"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ਕੀ ਵਾਪਸ <xliff:g id="CARRIER">%s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕਰਨਾ ਹੈ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ਮੋਬਾਈਲ ਡਾਟਾ ਉਪਲਬਧਤਾ ਦੇ ਆਧਾਰ \'ਤੇ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਸਵਿੱਚ ਨਹੀਂ ਹੋਵੇਗਾ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ਹਾਂ, ਸਵਿੱਚ ਕਰੋ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ਕਿਸੇ ਐਪ ਵੱਲੋਂ ਇਜਾਜ਼ਤ ਬੇਨਤੀ ਨੂੰ ਢਕੇ ਜਾਣ ਕਾਰਨ ਸੈਟਿੰਗਾਂ ਤੁਹਾਡੇ ਜਵਾਬ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕਰ ਸਕਦੀਆਂ।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"ਕੀ <xliff:g id="APP_0">%1$s</xliff:g> ਨੂੰ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੇਣੇ ਹਨ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ਇਹ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚੋਂ ਜਾਣਕਾਰੀ ਪੜ੍ਹ ਸਕਦਾ ਹੈ"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ਵੱਡਦਰਸ਼ੀ ਵਿੰਡੋ ਸੈਟਿੰਗਾਂ"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ। ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਇਹ ਬਟਨ ਵਿਉਂਤਬੱਧ ਕਰੋ ਜਾਂ ਬਦਲੋ।\n\n"<annotation id="link">"ਸੈਟਿੰਗਾਂ ਦੇਖੋ"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ਬਟਨ ਨੂੰ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਲੁਕਾਉਣ ਲਈ ਕਿਨਾਰੇ \'ਤੇ ਲਿਜਾਓ"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ਅਣਕੀਤਾ ਕਰੋ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}one{# ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}other{# ਸ਼ਾਰਟਕੱਟਾਂ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ਉੱਪਰ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ਉੱਪਰ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ਹੇਠਾਂ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ਹੇਠਾਂ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ਕਿਨਾਰੇ ਵਿੱਚ ਲਿਜਾ ਕੇ ਲੁਕਾਓ"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ਕਿਨਾਰੇ ਤੋਂ ਬਾਹਰ ਕੱਢ ਕੇ ਦਿਖਾਓ"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ਹਟਾਓ"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ਟੌਗਲ ਕਰੋ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ਡੀਵਾਈਸ ਕੰਟਰੋਲ"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਐਪ ਚੁਣੋ"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ਮੋਬਾਈਲ ਡਾਟਾ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ਕਨੈਕਟ ਹੈ"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ਕੁਝ ਸਮੇਂ ਲਈ ਕਨੈਕਟ ਹੈ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ਖਰਾਬ ਕਨੈਕਸ਼ਨ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ਮੋਬਾਈਲ ਡਾਟਾ ਸਵੈ-ਕਨੈਕਟ ਨਹੀਂ ਹੋਵੇਗਾ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ਕੋਈ ਕਨੈਕਸ਼ਨ ਨਹੀਂ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ਕੋਈ ਹੋਰ ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 8787f36..cb496fe 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nie można rozpoznać twarzy"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Użyj odcisku palca"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth połączony."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Poziom naładowania baterii jest nieznany."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Połączono z <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Zarządzaj użytkownikami"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Gotowe"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zamknij"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Połączono"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon jest dostępny"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Aparat jest dostępny"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i aparat są dostępne"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Inne urządzenie"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Przełącz Przegląd"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Nie będą Cię niepokoić żadne dźwięki ani wibracje z wyjątkiem alarmów, przypomnień, wydarzeń i połączeń od wybranych osób. Będziesz słyszeć wszystkie odtwarzane treści, takie jak muzyka, filmy czy gry."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Wyłączyć mobilną transmisję danych?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nie będziesz mieć dostępu do transmisji danych ani internetu w <xliff:g id="CARRIER">%s</xliff:g>. Internet będzie dostępny tylko przez Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Twój operator"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Wrócić do operatora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilna transmisja danych nie będzie automatycznie przełączana na podstawie dostępności"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nie, dziękuję"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Tak, wróć"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikacja Ustawienia nie może zweryfikować Twojej odpowiedzi, ponieważ inna aplikacja zasłania prośbę o udzielenie uprawnień."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Zezwolić aplikacji <xliff:g id="APP_0">%1$s</xliff:g> na pokazywanie wycinków z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Może odczytywać informacje z aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ustawienia okna powiększania"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Kliknij, aby otworzyć ułatwienia dostępu. Dostosuj lub zmień ten przycisk w Ustawieniach.\n\n"<annotation id="link">"Wyświetl ustawienia"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Przesuń przycisk do krawędzi, aby ukryć go tymczasowo"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Cofnij"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} skrót został usunięty}few{# skróty zostały usunięte}many{# skrótów zostało usuniętych}other{# skrótu został usunięte}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Przenieś w lewy górny róg"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Przenieś w prawy górny róg"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Przenieś w lewy dolny róg"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Przenieś w prawy dolny róg"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Przenieś do krawędzi i ukryj"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Przenieś poza krawędź i pokaż"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Usuń"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"przełącz"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Sterowanie urządzeniami"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Wybierz aplikację, do której chcesz dodać elementy sterujące"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilna transmisja danych"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Połączono"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tymczasowe połączenie"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Słabe połączenie"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna transmisja danych nie połączy się automatycznie"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Brak połączenia"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Brak innych dostępnych sieci"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index e6f0580..cbc8fc7a 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gerenciar usuários"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfone disponível"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Câmera disponível"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfone e câmera disponíveis"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfone ativado"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfone desativado"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O microfone está ativado para todos os apps e serviços."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acesso ao microfone está desativado para todos os apps e serviços. Ative em \"Configurações > Privacidade > Microfone\"."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acesso ao microfone está desativado para todos os apps e serviços. Você pode mudar isso em \"Configurações > Privacidade > Microfone\"."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Câmera ativada"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Câmera desativada"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A câmera está ativada para todos os apps e serviços."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acesso à câmera está desativado para todos os apps e serviços."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ative o acesso ao microfone nas Configurações para usar o botão dele."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir configurações."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Alternar Visão geral"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Você não será perturbado por sons e vibrações, exceto alarmes, lembretes, eventos e chamadas de pessoas especificadas. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Voltar para a operadora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"A conexão de dados móveis não vai ser alternada automaticamente de acordo com a disponibilidade"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Agora não"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, voltar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configurações da janela de lupa"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover para o canto superior direito"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover para o canto inferior esquerdo"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover para o canto inferior direito"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover para a borda e ocultar"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover para fora da borda e exibir"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remover"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alternar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporariamente conectado"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexão fraca"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 6c07ce0..d37ac01 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -143,7 +143,7 @@
<string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Rosto reconhecido. Prima para continuar."</string>
<string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Rosto reconhecido. Prima ícone de desbloqueio para continuar"</string>
<string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Autenticado"</string>
- <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Utilizar PIN"</string>
+ <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Usar PIN"</string>
<string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Utilizar padrão"</string>
<string name="biometric_dialog_use_password" msgid="3445033859393474779">"Utilizar palavra-passe"</string>
<string name="biometric_dialog_wrong_pin" msgid="1878539073972762803">"PIN incorreto."</string>
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Imposs. reconhecer rosto"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usar impressão digital"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ligado."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentagem da bateria desconhecida."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ligado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfone disponível"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Câmara disponível"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfone e câmara disponíveis"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfone ativado"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfone desativado"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O microfone está ativado para todas as apps e serviços."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acesso ao microfone está desativado para todas as apps e serviços. Pode ativar o acesso ao microfone em Definições > Privacidade > Microfone."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acesso ao microfone está desativado para todas as apps e serviços. Pode alterar esta opção em Definições > Privacidade > Microfone."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Câmara ativada"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Câmara desativada"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A câmara está ativada para todas as apps e serviços."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acesso à câmara está desativado para todas as apps e serviços."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para usar o botão do microfone, ative o acesso do microfone nas Definições."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir definições."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Ativar/desativar Vista geral"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Não é incomodado por sons e vibrações, exceto de alarmes, lembretes, eventos e autores de chamadas que especificar. Continua a ouvir tudo o que optar por reproduzir, incluindo música, vídeos e jogos."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando está a partilhar, gravar ou transmitir uma app, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partilhe ou grave uma app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Não terá acesso a dados ou à Internet através do operador <xliff:g id="CARRIER">%s</xliff:g>. A Internet estará disponível apenas por Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"o seu operador"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Mudar de novo para <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Os dados móveis não vão mudar automaticamente com base na disponibilidade"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Não"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, mudar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Uma vez que uma app está a ocultar um pedido de autorização, as Definições não conseguem validar a sua resposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permitir que a app <xliff:g id="APP_0">%1$s</xliff:g> mostre partes da app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações da app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Definições da janela da lupa"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir funcionalidades de acessibilidade. Personal. ou substitua botão em Defin.\n\n"<annotation id="link">"Ver defin."</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a extremidade para o ocultar temporariamente"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anular"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} atalho removido}many{# atalhos removidos}other{# atalhos removidos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover p/ parte sup. esquerda"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover parte superior direita"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover p/ parte infer. esquerda"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover parte inferior direita"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover p/ extremidade e ocultar"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Retirar extremidade e mostrar"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remover"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ativar/desativar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controlos de dispositivos"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Escolha uma app para adicionar controlos"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ligado"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ligado temporariamente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Ligação fraca"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem ligação automática com dados móveis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem ligação"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index e6f0580..cbc8fc7a 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gerenciar usuários"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfone disponível"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Câmera disponível"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfone e câmera disponíveis"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfone ativado"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfone desativado"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O microfone está ativado para todos os apps e serviços."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acesso ao microfone está desativado para todos os apps e serviços. Ative em \"Configurações > Privacidade > Microfone\"."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acesso ao microfone está desativado para todos os apps e serviços. Você pode mudar isso em \"Configurações > Privacidade > Microfone\"."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Câmera ativada"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Câmera desativada"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A câmera está ativada para todos os apps e serviços."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acesso à câmera está desativado para todos os apps e serviços."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ative o acesso ao microfone nas Configurações para usar o botão dele."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir configurações."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Alternar Visão geral"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Você não será perturbado por sons e vibrações, exceto alarmes, lembretes, eventos e chamadas de pessoas especificadas. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Voltar para a operadora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"A conexão de dados móveis não vai ser alternada automaticamente de acordo com a disponibilidade"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Agora não"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, voltar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configurações da janela de lupa"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover para o canto superior direito"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover para o canto inferior esquerdo"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover para o canto inferior direito"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover para a borda e ocultar"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover para fora da borda e exibir"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remover"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alternar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporariamente conectado"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexão fraca"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 080edfd..5667884 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Chip nerecunoscut"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Folosește amprenta"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Conectat prin Bluetooth."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procentajul bateriei este necunoscut."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectat la <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestionează utilizatorii"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Terminat"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Închide"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Conectat"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfon disponibil"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cameră foto disponibilă"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfon și cameră disponibile"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfonul a fost activat"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfonul a fost dezactivat"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microfonul este activat pentru toate aplicațiile și serviciile."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Accesul la microfon este dezactivat pentru toate aplicațiile și serviciile. Activează accesul la microfon în Setări > Confidențialitate > Microfon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Accesul la microfon este dezactivat pentru toate aplicațiile și serviciile. Modifică opțiunea în Setări > Confidențialitate > Microfon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera este activată"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera este dezactivată"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera este activată pentru toate aplicațiile și serviciile."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Accesul la cameră este dezactivat pentru toate aplicațiile și serviciile."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pentru a folosi butonul microfonului, activează accesul la microfon în Setări."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Deschide setările."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Alt dispozitiv"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comută secțiunea Recente"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Se vor anunța prin sunete și vibrații numai alarmele, mementourile, evenimentele și apelanții specificați de tine. Totuși, vei auzi tot ce alegi să redai, inclusiv muzică, videoclipuri și jocuri."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuă"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Permite accesul la o aplicație sau înregistreaz-o"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivezi datele mobile?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu vei avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul tău"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Revii la <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Comutarea la datele mobile nu se va face automat în funcție de disponibilitate"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nu, mulțumesc"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, fac trecerea"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu îți pot verifica răspunsul."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permiți ca <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setările ferestrei de mărire"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atinge ca să deschizi funcțiile de accesibilitate. Personalizează sau înlocuiește butonul în setări.\n\n"<annotation id="link">"Vezi setările"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mută butonul spre margine pentru a-l ascunde temporar"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anulează"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} comandă rapidă eliminată}few{# comenzi rapide eliminate}other{# de comenzi rapide eliminate}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mută în stânga sus"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mută în dreapta sus"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mută în stânga jos"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mută în dreapta jos"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mută la margine și ascunde"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mută de la margine și afișează"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Elimină"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Activează / dezactivează"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Comenzile dispozitivelor"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Alege aplicația pentru a adăuga comenzi"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Date mobile"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectat"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectat temporar"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexiune slabă"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nu se conectează automat la date mobile"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nicio conexiune"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nu sunt disponibile alte rețele"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index b42701e..3b820ec 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Лицо не распознано."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Используйте отпечаток."</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-соединение установлено."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Уровень заряда батареи в процентах неизвестен."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>: подключено."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Управление пользователями"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрыть"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Подключено"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон готов."</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера готова."</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон и камера готовы."</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофон включен"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофон отключен"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофон включен для всех приложений и сервисов."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Доступ к микрофону отключен для всех приложений и сервисов. Вы можете разрешить доступ к микрофону, перейдя в Настройки > Конфиденциальность > Микрофон."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Доступ к микрофону отключен для всех приложений и сервисов. Вы можете изменить этот параметр, перейдя в Настройки > Конфиденциальность > Микрофон."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера включена"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера отключена"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера включена для всех приложений и сервисов."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Доступ к камере отключен для всех приложений и сервисов."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Чтобы использовать кнопку микрофона, разрешите доступ к микрофону в настройках."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Открыть настройки"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Другое устройство"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Переключить режим обзора"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Вас не будут отвлекать звуки и вибрация, за исключением сигналов будильника, напоминаний, уведомлений о мероприятиях и звонков от помеченных контактов. Вы по-прежнему будете слышать включенную вами музыку, видео, игры и т. д."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далее"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Демонстрация экрана или запись видео с него"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Отключить мобильный Интернет?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Вы не сможете передавать данные или выходить в Интернет через оператора \"<xliff:g id="CARRIER">%s</xliff:g>\". Интернет будет доступен только по сети Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Переключиться на сеть \"<xliff:g id="CARRIER">%s</xliff:g>\"?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобильный интернет не будет переключаться автоматически."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Нет"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Невозможно принять ваше согласие, поскольку запрос скрыт другим приложением."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Разрешить приложению \"<xliff:g id="APP_0">%1$s</xliff:g>\" показывать фрагменты приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Ему станут доступны данные из приложения \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Настройка окна лупы"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Нажмите, чтобы открыть спец. возможности. Настройте или замените эту кнопку в настройках.\n\n"<annotation id="link">"Настройки"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Чтобы временно скрыть кнопку, переместите ее к краю экрана"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отменить"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} сочетание клавиш удалено}one{# сочетание клавиш удалено}few{# сочетания клавиш удалено}many{# сочетаний клавиш удалено}other{# сочетания клавиш удалено}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перенести в левый верхний угол"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перенести в правый верхний угол"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перенести в левый нижний угол"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перенести в правый нижний угол"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перенести к краю и скрыть"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Вернуть из-за края и показать"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Убрать"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"включить или отключить"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Управление устройствами"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Чтобы добавить виджеты управления, выберите приложение"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильный интернет"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Подключено"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Временное подключение"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слабый сигнал"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Без автоподключения к мобильному интернету"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Нет подключения к интернету"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нет других доступных сетей"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index bd272c7..5696980 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"මුහුණ හඳුනා ගත නොහැක"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත කරන්න"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"බ්ලූටූත් සම්බන්ධිතයි."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"බැටරි ප්රතිශතය නොදනී."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> වෙත සම්බන්ධ කරන ලදි."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"පරිශීලකයන් කළමනාකරණය කරන්න"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"නිමයි"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"වසන්න"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"සම්බන්ධිත"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"මයික්රෆෝනය ලබා ගත හැකිය"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"කැමරාව ලබා ගත හැකිය"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"මයික්රෆෝනය සහ කැමරාව ලබා ගත හැකිය"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"වෙනත් උපාංගය"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"දළ විශ්ලේෂණය ටොගල කරන්න"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"එලාම සිහිකැඳවීම්, සිදුවීම්, සහ ඔබ සඳහන් කළ අමතන්නන් හැර, ශබ්ද සහ කම්පනවලින් ඔබට බාධා නොවනු ඇත. සංගීතය, වීඩියෝ, සහ ක්රීඩා ඇතුළු ඔබ වාදනය කිරීමට තෝරන ලද සියලු දේ ඔබට තවම ඇසෙනු ඇත."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ඉදිරියට යන්න"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"යෙදුමක් බෙදා ගන්න හෝ පටිගත කරන්න"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ජංගම දත්ත ක්රියාවිරහිත කරන්නද?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ඔබට <xliff:g id="CARRIER">%s</xliff:g> හරහා දත්ත හෝ අන්තර්ජාලයට පිවිසීමේ හැකියාවක් නැත. අන්තර්ජාලය Wi-Fi හරහා පමණක් ලබා ගත හැකිය."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ඔබගේ වාහකය"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> වෙත ආපසු මාරු කරන්නද?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ජංගම දත්ත ලබා ගත හැකි වීමට අනුව ස්වයංක්රීයව මාරු නොවෙයි"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"එපා ස්තුතියි"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ඔව්, මාරු කරන්න"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"යෙදුමක් අවසර ඉල්ලීමක් කරන නිසා, සැකසීම්වලට ඔබගේ ප්රතිචාරය සත්යාපනය කළ නොහැකිය."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට ඉඩ දෙන්නද?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- එයට <xliff:g id="APP">%1$s</xliff:g> වෙතින් තොරතුරු කියවිය හැකිය"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"විශාලන කවුළු සැකසීම්"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ප්රවේශ්යතා විශේෂාංග විවෘත කිරීමට තට්ටු කරන්න. සැකසීම් තුළ මෙම බොත්තම අභිරුචිකරණය හෝ ප්රතිස්ථාපනය කරන්න.\n\n"<annotation id="link">"සැකසීම් බලන්න"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"එය තාවකාලිකව සැඟවීමට බොත්තම දාරයට ගෙන යන්න"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"අස් කරන්න"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} කෙටිමඟ ඉවත් කළා}one{කෙටිමං # ක් ඉවත් කළා}other{කෙටිමං # ක් ඉවත් කළා}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ඉහළ වමට ගෙන යන්න"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ඉහළ දකුණට ගෙන යන්න"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"පහළ වමට ගෙන යන්න"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"පහළ දකුණට ගෙන යන්න"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"මායිමට ගෙන යන්න සහ සඟවන්න"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"මායිමෙන් පිටට ගන්න සහ පෙන්වන්න"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ඉවත් කරන්න"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ටොගල් කරන්න"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"උපාංග පාලන"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"පාලන එක් කිරීමට යෙදුම තෝරා ගන්න"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ජංගම දත්ත"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"සම්බන්ධයි"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"තාවකාලිකව සම්බන්ධ කළා"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"දුර්වල සම්බන්ධතාව"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ජංගම දත්ත ස්වංක්රියව සම්බන්ධ නොවනු ඇත"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"සම්බන්ධතාවයක් නැත"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ලබා ගත හැකි වෙනත් ජාල නැත"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index a0dd3c3..6aaafc1 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Tvár sa nedá rozpoznať"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Používať radšej odtlačok"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth pripojené."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percento batérie nie je známe."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Pripojené k zariadeniu <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Spravovať používateľov"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zavrieť"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Pripojené"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofón je k dispozícii"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je k dispozícii"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofón a kamera sú k dispozícii"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofón je zapnutý"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofón je vypnutý"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofón je povolený pre všetky aplikácie a služby."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Prístup k mikrofónu bol zakázaný pre všetky aplikácie a služby. Môžete ho povoliť v sekcii Nastavenia > Ochrana súkromia > Mikrofón."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Prístup k mikrofónu bol zakázaný pre všetky aplikácie a služby. Môžete to zmeniť v sekcii Nastavenia > Ochrana súkromia > Mikrofón."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera je zapnutá"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera je vypnutá"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera je povolená pre všetky aplikácie a služby."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Prístup ku kamere je zakázaný pre všetky aplikácie a služby."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ak chcete použiť tlačidlo mikrofónu, povoľte prístup k mikrofónu v Nastaveniach."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otvoriť nastavenia"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Iné zariadenie"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Prepnúť prehľad"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Nebudú vás vyrušovať zvuky ani vibrácie, iba budíky, pripomenutia, udalosti a volajúci, ktorých určíte. Budete naďalej počuť všetko, čo sa rozhodnete prehrať, ako napríklad hudbu, videá a hry."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Počas zdieľania, nahrávania alebo prenosu bude mať aplikácia <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovať"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Aplikácia na zdieľanie alebo nahrávanie"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"História"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Chcete vypnúť mobilné dáta?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nebudete mať prístup k dátam ani internetu prostredníctvom operátora <xliff:g id="CARRIER">%s</xliff:g>. Internet bude k dispozícii iba cez Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"váš operátor"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Chcete prepnúť späť na operátora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilné dáta sa nebudú automaticky prepínať na základe dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nie, vďaka"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Áno, prepnúť"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Nastavenia nemôžu overiť vašu odpoveď, pretože určitá aplikácia blokuje žiadosť o povolenie."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Povoliť aplikácii <xliff:g id="APP_0">%1$s</xliff:g> zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Môže čítať informácie z aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavenia okna lupy"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Funkcie dostupnosti otvoríte klepnutím. Tlačidlo prispôsobte alebo nahraďte v Nastav.\n\n"<annotation id="link">"Zobraz. nast."</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ak chcete tlačidlo dočasne skryť, presuňte ho k okraju"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Späť"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Bola odstránená skratka {label}}few{Boli odstránené # skratky}many{# shortcuts removed}other{Bolo odstránených # skratiek}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Presunúť doľava nahor"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Presunúť doprava nahor"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Presunúť doľava nadol"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Presunúť doprava nadol"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Presunúť k okraju a skryť"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Presunúť z okraja a zobraziť"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Odstrániť"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"prepínač"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Ovládanie zariadení"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikáciu, ktorej ovládače si chcete pridať"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilné dáta"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Pripojené"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Dočasne pripojené"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slabé pripojenie"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Automatické pripojenie cez mobilné dáta nefunguje"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Bez pripojenia"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nie sú k dispozícii žiadne ďalšie siete"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 44108c3..36eead3 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Obraz ni bil prepoznan."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Uporabite prstni odtis."</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Povezava Bluetooth vzpostavljena."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Neznan odstotek napolnjenosti baterije."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezava vzpostavljena z: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Upravljanje uporabnikov"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Končano"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zapri"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Povezava je vzpostavljena"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je na voljo"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je na voljo"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon in kamera sta na voljo"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Druga naprava"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Vklop/izklop pregleda"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Ne bodo vas motili zvoki ali vibriranje, razen v primeru alarmov, opomnikov, dogodkov in klicateljev, ki jih določite. Še vedno pa boste slišali vse, kar se boste odločili predvajati, vključno z glasbo, videoposnetki in igrami."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Pri deljenju, snemanju ali predvajanju aplikacije ima aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Naprej"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deljenje ali snemanje aplikacije"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljaj"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Zgodovina"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite izklopiti prenos podatkov v mobilnih omrežjih?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Prek operaterja »<xliff:g id="CARRIER">%s</xliff:g>« ne boste imeli dostopa do podatkovne povezave ali interneta. Internet bo na voljo samo prek povezave Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"svojega operaterja"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Želite preklopiti nazaj na ponudnika <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Prenos podatkov v mobilnem omrežju ne preklopi samodejno glede na razpoložljivost."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, preklopi"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Ker aplikacija zakriva zahtevo za dovoljenje, z nastavitvami ni mogoče preveriti vašega odziva."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Želite dovoliti, da aplikacija <xliff:g id="APP_0">%1$s</xliff:g> prikaže izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– lahko bere podatke v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavitve okna povečevalnika"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dotaknite se za funkcije za ljudi s posebnimi potrebami. Ta gumb lahko prilagodite ali zamenjate v nastavitvah.\n\n"<annotation id="link">"Ogled nastavitev"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Če želite gumb začasno skriti, ga premaknite ob rob."</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Razveljavi"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Odstranjena bližnjica za fun. {label}}one{Odstranjena # bližnjica}two{Odstranjeni # bližnjici}few{Odstranjene # bližnjice}other{Odstranjenih # bližnjic}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premakni zgoraj levo"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premakni zgoraj desno"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premakni spodaj levo"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premakni spodaj desno"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premakni na rob in skrij"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Premakni z roba in pokaži"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Odstrani"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"preklop"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrolniki naprave"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Izberite aplikacijo za dodajanje kontrolnikov"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Prenos podatkov v mobilnem omrežju"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Začasno vzpostavljena povezava"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba povezava"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna podatkovna povezava ne bo samodejna."</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ni povezave"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nobeno drugo omrežje ni na voljo"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 7630261..e73a322 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Fytyra nuk mund të njihet"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Përdor më mirë gjurmën e gishtit"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Pajisja është lidhur me \"bluetooth\"."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Përqindja e baterisë e panjohur."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Lidhur me <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Menaxho përdoruesit"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"U krye"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Mbyll"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"I lidhur"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofoni ofrohet"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera ofrohet"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofoni dhe kamera ofrohen"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofoni u aktivizua"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofoni u çaktivizua"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofoni është aktivizuar për të gjitha aplikacionet dhe shërbimet."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Qasja te mikrofoni është çaktivizuar për të gjitha aplikacionet dhe shërbimet. Mund ta aktivizosh qasjen te mikrofoni te \"Cilësimet > Privatësia > Mikrofoni\"."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Qasja te mikrofoni është çaktivizuar për të gjitha aplikacionet dhe shërbimet. Këtë mund ta ndryshosh te \"Cilësimet > Privatësia > Mikrofoni\"."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera u aktivizua"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera u çaktivizua"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera është aktivizuar për të gjitha aplikacionet dhe shërbimet."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Qasja te kamera është çaktivizuar për të gjitha aplikacionet dhe shërbimet."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Për të përdorur butonin e mikrofonit, aktivizo qasjen te mikrofoni te \"Cilësimet\"."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Hap cilësimet."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Pajisje tjetër"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Kalo te përmbledhja"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Nuk do të shqetësohesh nga tingujt dhe dridhjet, përveç alarmeve, alarmeve rikujtuese, ngjarjeve dhe telefonuesve që specifikon. Do të vazhdosh të dëgjosh çdo gjë që zgjedh të luash duke përfshirë muzikën, videot dhe lojërat."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Vazhdo"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shpërndaj ose regjistro një aplikacion"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Të çaktivizohen të dhënat celulare?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nuk do të kesh qasje te të dhënat ose interneti nëpërmjet <xliff:g id="CARRIER">%s</xliff:g>. Interneti do të ofrohet vetëm nëpërmjet Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatori yt celular"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Të kalohet përsëri te <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Të dhënat celulare nuk do të ndërrohen automatikisht në bazë të disponueshmërisë"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Jo, faleminderit"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Po, ndërro"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Duke qenë se një aplikacion po bllokon një kërkesë për leje, \"Cilësimet\" nuk mund të verifikojnë përgjigjen tënde."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Të lejohet <xliff:g id="APP_0">%1$s</xliff:g> që të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Mund të lexojë informacion nga <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Cilësimet e dritares së zmadhimit"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trokit dhe hap veçoritë e qasshmërisë. Modifiko ose ndërro butonin te \"Cilësimet\".\n\n"<annotation id="link">"Shih cilësimet"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Zhvendose butonin në skaj për ta fshehur përkohësisht"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Zhbëj"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Shkurtorja {label} u hoq}other{# shkurtore u hoqën}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Zhvendos lart majtas"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Zhvendos lart djathtas"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Zhvendos poshtë majtas"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Zhvendos poshtë djathtas"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Zhvendose te skaji dhe fshihe"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Zhvendose jashtë skajit dhe shfaqe"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Hiq"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktivizo/çaktivizo"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrollet e pajisjes"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Zgjidh aplikacionin për të shtuar kontrollet"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Të dhënat celulare"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Lidhur"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Lidhur përkohësisht"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Lidhje e dobët"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Të dhënat celulare nuk do të lidhen automatikisht"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nuk ka lidhje"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nuk ofrohet asnjë rrjet tjetër"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 2a7b88f..6d0c201 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Лице није препознато"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користите отисак прста"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth је прикључен."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Проценат напуњености батерије није познат."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Повезани сте са <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Управљаjте корисницима"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затвори"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Повезан"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон је доступан"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера је доступна"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон и камера су доступни"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Други уређај"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Укључи/искључи преглед"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Неће вас узнемиравати звукови и вибрације осим за аларме, подсетнике, догађаје и позиваоце које наведете. И даље ћете чути све што одаберете да пустите, укључујући музику, видео снимке и игре."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Настави"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Делите или снимите апликацију"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управљајте"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Желите да искључите мобилне податке?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Нећете имати приступ подацима или интернету преко мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>. Интернет ће бити доступан само преко WiFi везе."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"мобилни оператер"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Желите да се вратите на мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобилни подаци се неће аутоматски променити на основу доступности"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, хвала"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, пређи"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Подешавања не могу да верификују ваш одговор јер апликација скрива захтев за дозволу."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чита податке из апликације <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Подешавања прозора за увећање"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Додирните за функције приступачности. Прилагодите или замените ово дугме у Подешавањима.\n\n"<annotation id="link">"Подешавања"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Померите дугме до ивице да бисте га привремено сакрили"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Опозовите"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} пречица је уклоњена}one{# пречица је уклоњена}few{# пречице су уклоњене}other{# пречица је уклоњено}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Премести горе десно"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Премести доле лево"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Премести доле десно"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Премести до ивице и сакриј"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Премести изван ивице и прикажи"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Уклоните"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"укључите/искључите"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Контроле уређаја"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Одаберите апликацију за додавање контрола"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни подаци"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Повезано"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Привремено повезано"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Веза је лоша"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Није успело аутом. повезивање преко моб. података"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Веза није успостављена"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Није доступна ниједна друга мрежа"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 48654d5..2eac10f 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet kändes inte igen"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Använd fingeravtryck"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ansluten."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Okänd batterinivå."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ansluten till <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Hantera användare"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Klart"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Stäng"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Ansluten"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofonen kan användas"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kameran kan användas"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofonen och kameran kan användas"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonen sattes på"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonen stängdes av"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonen är aktiverad för alla appar och tjänster."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonåtkomst är inaktiverad för alla appar och tjänster. Du kan aktivera mikrofonåtkomst i Inställningar > Integritet > Mikrofon."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonåtkomst är inaktiverad för alla appar och tjänster. Du kan ändra detta i Inställningar > Integritet > Mikrofon."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kameran sattes på"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kameran stängdes av"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameran är aktiverad för alla appar och tjänster."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kameraåtkomst är inaktiverad för alla appar och tjänster."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Aktivera mikrofonåtkomst i inställningarna om du vill använda mikrofonknappen."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Öppna inställningarna."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Annan enhet"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aktivera och inaktivera översikten"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Du blir inte störd av ljud och vibrationer, förutom från alarm, påminnelser, händelser och specifika samtal. Ljudet är fortfarande på för sådant du väljer att spela upp, till exempel musik, videor och spel."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"När du delar, spelar in eller castar en app har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsätt"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dela eller spela in en app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vill du inaktivera mobildata?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kan inte skicka data eller använda internet via <xliff:g id="CARRIER">%s</xliff:g>. Internetanslutning blir bara möjlig via wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"din operatör"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vill du byta tillbaka till <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobildatakällan byts inte automatiskt efter tillgänglighet"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nej tack"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, byt"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Svaret kan inte verifieras av Inställningar eftersom en app skymmer en begäran om behörighet."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Tillåter du att bitar av <xliff:g id="APP_2">%2$s</xliff:g> visas i <xliff:g id="APP_0">%1$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Kan läsa information från <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Inställningar för förstoringsfönster"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryck för att öppna tillgänglighetsfunktioner. Anpassa/ersätt knappen i Inställningar.\n\n"<annotation id="link">"Inställningar"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytta knappen till kanten för att dölja den tillfälligt"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ångra"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} genväg har tagits bort}other{# genvägar har tagits bort}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytta högst upp till vänster"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flytta högst upp till höger"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flytta längst ned till vänster"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flytta längst ned till höger"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flytta till kanten och dölj"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flytta från kanten och visa"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Ta bort"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktivera och inaktivera"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyrning"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Välj en app om du vill lägga till snabbkontroller"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ansluten"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tillfälligt ansluten"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dålig anslutning"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Du ansluts inte till mobildata automatiskt"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen anslutning"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Inga andra nätverk är tillgängliga"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 6bf2bc2..47fbdc43 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Imeshindwa kutambua uso"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Badala yake, tumia alama ya kidole"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth imeunganishwa."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Asilimia ya betri haijulikani."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Imeunganishwa kwenye <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Dhibiti watumiaji"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Nimemaliza"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Funga"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Imeunganishwa"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Maikrofoni inapatikana"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera inapatikana"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Maikrofoni na kamera zinapatikana"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Umewasha maikrofoni"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Umezima maikrofoni"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Umewasha maikrofoni kwenye programu na huduma zote."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Umezima ufikiaji wa maikrofoni kwenye programu na huduma zote. Unaweza kuruhusu ufikiaji wa maikrofoni kwenye Mipangilio > Faragha > Maikrofoni."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Umezima ufikiaji wa maikrofoni kwenye programu na huduma zote. Unaweza kubadilisha hali hii kwenye Mipangilio > Faragha > Maikrofoni."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Umewasha kamera"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Umezima kamera"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Umewasha kamera kwenye programu na huduma zote."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Umezima ufikiaji wa kamera kwenye programu na huduma zote."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ili utumie kitufe cha maikrofoni, ruhusu ufikiaji wa maikrofoni kwenye Mipangilio."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Fungua mipangilio."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Kifaa kingine"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Washa Muhtasari"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Hutasumbuliwa na sauti na mitetemo, isipokuwa kengele, vikumbusho, matukio na simu zinazopigwa na watu uliobainisha. Bado utasikia chochote utakachochagua kucheza, ikiwa ni pamoja na muziki, video na michezo."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Unapotuma, kurekodi au kushiriki programu, programu ya <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Endelea"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shiriki au rekodi programu"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Dhibiti"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Ungependa kuzima data ya mtandao wa simu?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Hutaweza kufikia data au intaneti kupitia <xliff:g id="CARRIER">%s</xliff:g>. Intaneti itapatikana kupitia Wi-Fi pekee."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mtoa huduma wako"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Ungependa kubadilisha ili utumie data ya mtandao wa <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data ya mtandao wa simu haitabadilika kiotomatiki kulingana na upatikanaji"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hapana"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ndiyo, badili"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Kwa sababu programu nyingine inazuia ombi la ruhusa, hatuwezi kuthibitisha jibu lako katika Mipangilio."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Ungependa kuruhusu <xliff:g id="APP_0">%1$s</xliff:g> ionyeshe vipengee <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Inaweza kusoma maelezo kutoka <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Mipangilio ya dirisha la kikuzaji"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Gusa ili ufungue vipengele vya ufikivu. Weka mapendeleo au ubadilishe kitufe katika Mipangilio.\n\n"<annotation id="link">"Angalia mipangilio"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sogeza kitufe kwenye ukingo ili ukifiche kwa muda"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Tendua"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Njia {label} ya mkato imeondolewa}other{Njia # za mkato zimeondolewa}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sogeza juu kushoto"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sogeza juu kulia"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sogeza chini kushoto"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sogeza chini kulia"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Sogeza kwenye ukingo kisha ufiche"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Sogeza nje ya ukingo kisha uonyeshe"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Ondoa"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"geuza"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Vidhibiti vya vifaa"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Chagua programu ili uweke vidhibiti"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Data ya mtandao wa simu"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Imeunganishwa"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Imeunganishwa kwa muda"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Muunganisho duni"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Data ya mtandao wa simu haitaunganishwa kiotomatiki"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Hakuna muunganisho"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Hakuna mitandao mingine inayopatikana"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index d9df337..707bc9e 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,7 +17,6 @@
<resources>
<dimen name="notification_panel_margin_horizontal">48dp</dimen>
<dimen name="status_view_margin_horizontal">62dp</dimen>
- <dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
<!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
pages is margin * 2, and that makes tiles page not appear immediately after user swipes to
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
index 97ead01..b98165f 100644
--- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -21,4 +21,6 @@
<!-- Space between status view and notification shelf -->
<dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
<dimen name="keyguard_clock_top_margin">80dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 17f82b5..8b41a44 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,7 +21,6 @@
for different hardware and product builds. -->
<resources>
<dimen name="status_view_margin_horizontal">124dp</dimen>
- <dimen name="bouncer_user_switcher_y_trans">200dp</dimen>
<dimen name="large_screen_shade_header_left_padding">24dp</dimen>
<dimen name="qqs_layout_padding_bottom">40dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index b291004..f836e95 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"முகத்தை கண்டறிய இயலவில்லை"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"கைரேகையை உபயோகிக்கவும்"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"புளூடூத் இணைக்கப்பட்டது."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"பேட்டரி சதவீதம் தெரியவில்லை."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>க்கு இணைக்கப்பட்டது."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"பயனர்களை நிர்வகியுங்கள்"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"முடிந்தது"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"மூடுக"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"இணைக்கப்பட்டது"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"மைக்ரோஃபோன் அணுகல் உள்ளது"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"கேமரா அணுகல் உள்ளது"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"மைக்ரோஃபோன் & கேமரா அணுகல் உள்ளது"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"மைக்ரோஃபோன் அணுகல் இயக்கப்பட்டது"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"மைக்ரோஃபோன் அணுகல் முடக்கப்பட்டது"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் மைக்ரோஃபோன் அணுகல் இயக்கப்பட்டது."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் மைக்ரோஃபோன் அணுகல் முடக்கப்பட்டது. அமைப்புகள் > தனியுரிமை > மைக்ரோஃபோன் என்பதற்குச் சென்று மைக்ரோஃபோன் அணுகலை இயக்கிக்கொள்ளலாம்."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் மைக்ரோஃபோன் அணுகல் முடக்கப்பட்டது. அமைப்புகள் > தனியுரிமை > மைக்ரோஃபோன் என்பதற்குச் சென்று இதை மாற்றிக்கொள்ளலாம்."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"கேமரா அணுகல் இயக்கப்பட்டது"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"கேமரா அணுகல் முடக்கப்பட்டது"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் கேமரா அணுகல் இயக்கப்பட்டது."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் கேமரா அணுகல் முடக்கப்பட்டது."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"மைக்ரோஃபோன் பட்டனைப் பயன்படுத்த, மைக்ரோஃபோன் அணுகலை அமைப்புகளில் இயக்கவும்."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"அமைப்புகளைத் திற."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"பிற சாதனம்"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"மேலோட்டப் பார்வையை நிலைமாற்று"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"அலாரங்கள், நினைவூட்டல்கள், நிகழ்வுகள் மற்றும் குறிப்பிட்ட அழைப்பாளர்களைத் தவிர்த்து, பிற ஒலிகள் மற்றும் அதிர்வுகளின் தொந்தரவு இருக்காது. எனினும், நீங்கள் எதையேனும் (இசை, வீடியோக்கள், கேம்ஸ் போன்றவை) ஒலிக்கும்படி தேர்ந்தெடுத்திருந்தால், அவை வழக்கம் போல் ஒலிக்கும்."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"தொடர்க"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ஆப்ஸைப் பகிர்தல் அல்லது ரெக்கார்டு செய்தல்"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"நிர்வகி"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"இதுவரை வந்த அறிவிப்புகள்"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"மொபைல் டேட்டாவை ஆஃப் செய்யவா?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> மூலம் டேட்டா அல்லது இணையத்தை உங்களால் பயன்படுத்த முடியாது. வைஃபை வழியாக மட்டுமே இணையத்தைப் பயன்படுத்த முடியும்."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"உங்கள் மொபைல் நிறுவனம்"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>க்கு மறுபடியும் மாற்றவா?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"கிடைக்கும் நிலையின் அடிப்படையில் மொபைல் டேட்டா தானாகவே மாறாது"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"வேண்டாம்"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"மாற்று"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"அனுமதிக் கோரிக்கையை ஆப்ஸ் மறைப்பதால், அமைப்புகளால் உங்கள் பதிலைச் சரிபார்க்க முடியாது."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ஆப்ஸை, <xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க அனுமதிக்கவா?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிலிருந்து தகவலைப் படிக்கும்"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"சாளரத்தைப் பெரிதாக்கும் கருவிக்கான அமைப்புகள்"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"அணுகல்தன்மை அம்சத்தை திறக்க தட்டவும். அமைப்பில் பட்டனை பிரத்தியேகமாக்கலாம்/மாற்றலாம்.\n\n"<annotation id="link">"அமைப்பில் காண்க"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"பட்டனைத் தற்காலிகமாக மறைக்க ஓரத்திற்கு நகர்த்தும்"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"செயல்தவிர்"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ஷார்ட்கட் அகற்றப்பட்டது}other{# ஷார்ட்கட்கள் அகற்றப்பட்டன}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"மேலே இடதுபுறத்திற்கு நகர்த்து"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"மேலே வலதுபுறத்திற்கு நகர்த்து"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"கீழே இடதுபுறத்திற்கு நகர்த்து"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"கீழே வலதுபுறத்திற்கு நகர்த்து"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ஓரத்திற்கு நகர்த்தி மறை"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ஓரத்திற்கு நகர்த்தி, காட்டு"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"அகற்று"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"நிலைமாற்று"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"சாதனக் கட்டுப்பாடுகள்"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"கட்டுப்பாடுகளைச் சேர்க்க வேண்டிய ஆப்ஸைத் தேர்ந்தெடுங்கள்"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"மொபைல் டேட்டா"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"இணைக்கப்பட்டது"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"தற்காலிகமாக இணைக்கப்பட்டுள்ளது"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"இணைப்பு மோசமாக உள்ளது"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"மொபைல் டேட்டாவுடன் தானாக இணைக்காது"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"இணைப்பு இல்லை"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"வேறு நெட்வொர்க்குகள் எதுவும் கிடைக்கவில்லை"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 4c34f45..002a4b2 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ముఖం గుర్తించడం కుదరలేదు"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"బదులుగా వేలిముద్రను ఉపయోగించండి"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"బ్లూటూత్ కనెక్ట్ చేయబడింది."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"బ్యాటరీ శాతం తెలియదు."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"యూజర్లను మేనేజ్ చేయండి"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"పూర్తయింది"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"మూసివేయి"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"కనెక్ట్ చేయబడినది"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"మైక్రోఫోన్ అందుబాటులో ఉంది"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"కెమెరా అందుబాటులో ఉంది"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"మైక్రోఫోన్, అలాగే కెమెరా అందుబాటులో ఉంది"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"మైక్రోఫోన్ ఆన్ చేయబడింది"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"మైక్రోఫోన్ ఆఫ్ చేయబడింది"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"యాప్లు, అలాగే సర్వీస్లన్నింటికీ మైక్రోఫోన్ ఎనేబుల్ చేయబడింది."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"యాప్లు, అలాగే సర్వీస్లన్నింటికీ మైక్రోఫోన్ యాక్సెస్ డిజేబుల్ చేయబడింది. సెట్టింగ్లు > గోప్యత > మైక్రోఫోన్లో మీరు మైక్రోఫోన్ యాక్సెస్ను ఎనేబుల్ చేయవచ్చు."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"యాప్లు, అలాగే సర్వీస్లన్నింటికీ మైక్రోఫోన్ యాక్సెస్ డిజేబుల్ చేయబడింది. సెట్టింగ్లు > గోప్యత > మైక్రోఫోన్లో మీరు దీన్ని మార్చవచ్చు."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"కెమెరా ఆన్ చేయబడింది"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"కెమెరా ఆఫ్ చేయబడింది"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"యాప్లు, అలాగే సర్వీస్లన్నింటికీ కెమెరా ఎనేబుల్ చేయబడింది."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"యాప్లు, అలాగే సర్వీస్లన్నింటికీ కెమెరా యాక్సెస్ డిజేబుల్ చేయబడింది."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"మైక్రోఫోన్ బటన్ను ఉపయోగించడానికి, సెట్టింగ్లలో మైక్రోఫోన్ యాక్సెస్ను ఎనేబుల్ చేయండి."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"సెట్టింగ్లను తెరవండి."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"ఇతర పరికరం"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"స్థూలదృష్టిని టోగుల్ చేయి"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"మీరు పేర్కొనే అలారాలు, రిమైండర్లు, ఈవెంట్లు మరియు కాలర్ల నుండి మినహా మరే ఇతర ధ్వనులు మరియు వైబ్రేషన్లతో మీకు అంతరాయం కలగదు. మీరు ఇప్పటికీ సంగీతం, వీడియోలు మరియు గేమ్లతో సహా మీరు ప్లే చేయడానికి ఎంచుకున్నవి ఏవైనా వింటారు."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"మీరు ఏదైనా యాప్ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>కు యాక్సెస్ ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"కొనసాగించండి"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"యాప్ను షేర్ చేయండి లేదా రికార్డ్ చేయండి"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"మేనేజ్ చేయండి"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"హిస్టరీ"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"మొబైల్ డేటాను ఆఫ్ చేయాలా?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"\"<xliff:g id="CARRIER">%s</xliff:g>\" ద్వారా మీకు డేటా లేదా ఇంటర్నెట్కు యాక్సెస్ ఉండదు. Wi-Fi ద్వారా మాత్రమే ఇంటర్నెట్ అందుబాటులో ఉంటుంది."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"మీ క్యారియర్"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>కి తిరిగి మారాలా?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"మొబైల్ డేటా లభ్యత ఆధారంగా ఆటోమేటిక్గా స్విచ్ అవ్వదు"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"వద్దు, థ్యాంక్స్"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"అవును, మార్చండి"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"అనుమతి రిక్వెస్ట్కు ఒక యాప్ అడ్డు తగులుతున్నందున సెట్టింగ్లు మీ ప్రతిస్పందనను ధృవీకరించలేకపోయాయి."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> స్లైస్లను చూపించడానికి <xliff:g id="APP_0">%1$s</xliff:g>ని అనుమతించండి?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ఇది <xliff:g id="APP">%1$s</xliff:g> నుండి సమాచారాన్ని చదువుతుంది"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"మాగ్నిఫయర్ విండో సెట్టింగ్లు"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"యాక్సెసిబిలిటీ ఫీచర్లను తెరవడానికి ట్యాప్ చేయండి. సెట్టింగ్లలో ఈ బటన్ను అనుకూలంగా మార్చండి లేదా రీప్లేస్ చేయండి.\n\n"<annotation id="link">"వీక్షణ సెట్టింగ్లు"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"తాత్కాలికంగా దానిని దాచడానికి బటన్ను చివరకు తరలించండి"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"చర్య రద్దు చేయండి"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} షార్ట్కట్ తీసివేయబడింది}other{# షార్ట్కట్లు తీసివేయబడ్డాయి}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ఎగువ ఎడమ వైపునకు తరలించు"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ఎగువ కుడి వైపునకు తరలించు"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"దిగువ ఎడమ వైపునకు తరలించు"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"దిగువ కుడి వైపునకు తరలించు"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"అంచుకు తరలించి దాచండి"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"అంచుని తరలించి చూపించు"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"తీసివేయండి"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"టోగుల్ చేయి"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"డివైజ్ కంట్రోల్స్"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"కంట్రోల్స్ను యాడ్ చేయడానికి యాప్ను ఎంచుకోండి"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"మొబైల్ డేటా"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"కనెక్ట్ చేయబడింది"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"తాత్కాలికంగా కనెక్ట్ చేయబడింది"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"కనెక్షన్ బాగాలేదు"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"మొబైల్ డేటా ఆటోమెటిక్గా కనెక్ట్ అవ్వదు"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"కనెక్షన్ లేదు"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ఇతర నెట్వర్క్లేవీ అందుబాటులో లేవు"</string>
diff --git a/packages/SystemUI/res/values-television/strings.xml b/packages/SystemUI/res/values-television/strings.xml
new file mode 100644
index 0000000..f30b73e
--- /dev/null
+++ b/packages/SystemUI/res/values-television/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+ <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+ \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
+ </string>
+
+ <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+ <string name="log_access_confirmation_learn_more" translatable="false"></string>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-television/styles.xml b/packages/SystemUI/res/values-television/styles.xml
index 12020f9..c517845 100644
--- a/packages/SystemUI/res/values-television/styles.xml
+++ b/packages/SystemUI/res/values-television/styles.xml
@@ -63,4 +63,10 @@
<item name="android:paddingVertical">@dimen/bottom_sheet_button_padding_vertical</item>
<item name="android:stateListAnimator">@anim/tv_bottom_sheet_button_state_list_animator</item>
</style>
+
+ <!-- The style for log access consent button -->
+ <style name="LogAccessDialogTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+ <item name="permissionGrantButtonTopStyle">?android:buttonBarButtonStyle</item>
+ <item name="permissionGrantButtonBottomStyle">?android:buttonBarButtonStyle</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 5de908c..fa8118e 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ไม่รู้จักใบหน้า"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ใช้ลายนิ้วมือแทน"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"เชื่อมต่อบลูทูธแล้ว"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ไม่ทราบเปอร์เซ็นต์แบตเตอรี่"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"เชื่อมต่อกับ <xliff:g id="BLUETOOTH">%s</xliff:g> แล้ว"</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ใช้งานไมโครโฟนได้"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ใช้งานกล้องได้"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ใช้งานไมโครโฟนและกล้องได้"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"เปิดไมโครโฟนแล้ว"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ปิดไมโครโฟนแล้ว"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"เปิดไมโครโฟนสำหรับแอปและบริการทั้งหมดแล้ว"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ปิดการเข้าถึงไมโครโฟนสำหรับแอปและบริการทั้งหมดแล้ว คุณสามารถเปิดการเข้าถึงไมโครโฟนได้จากการตั้งค่า > ความเป็นส่วนตัว > ไมโครโฟน"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ปิดการเข้าถึงไมโครโฟนสำหรับแอปและบริการทั้งหมดแล้ว คุณเปลี่ยนการตั้งค่านี้ได้ในการตั้งค่า > ความเป็นส่วนตัว > ไมโครโฟน"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"เปิดกล้องแล้ว"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ปิดกล้องแล้ว"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"เปิดการเข้าถึงกล้องสำหรับแอปและบริการทั้งหมดแล้ว"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ปิดการเข้าถึงกล้องสำหรับแอปและบริการทั้งหมดแล้ว"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"หากต้องการใช้ปุ่มไมโครโฟน ให้เปิดการเข้าถึงไมโครโฟนในการตั้งค่า"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"เปิดการตั้งค่า"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"อุปกรณ์อื่น"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"สลับภาพรวม"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"คุณจะไม่ถูกรบกวนจากเสียงและการสั่น ยกเว้นเสียงนาฬิกาปลุก การช่วยเตือน กิจกรรม และผู้โทรที่ระบุไว้ คุณจะยังคงได้ยินสิ่งที่คุณเลือกเล่น เช่น เพลง วิดีโอ และเกม"</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ต่อไป"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"แชร์หรือบันทึกแอป"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"จัดการ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ประวัติ"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ปิดอินเทอร์เน็ตมือถือไหม"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"คุณจะใช้เน็ตมือถือหรืออินเทอร์เน็ตผ่าน \"<xliff:g id="CARRIER">%s</xliff:g>\" ไม่ได้ แต่จะใช้ผ่าน Wi-Fi ได้เท่านั้น"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ผู้ให้บริการของคุณ"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"เปลี่ยนกลับเป็น <xliff:g id="CARRIER">%s</xliff:g> หรือไม่"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"อินเทอร์เน็ตมือถือไม่ได้เปลี่ยนตามความพร้อมบริการโดยอัตโนมัติ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ไม่เป็นไร"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ใช่ เปลี่ยนเลย"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"เนื่องจากแอปหนึ่งได้บดบังคำขอสิทธิ์ ระบบจึงไม่สามารถยืนยันคำตอบของคุณสำหรับการตั้งค่าได้"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"อนุญาตให้ <xliff:g id="APP_0">%1$s</xliff:g> แสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- อ่านข้อมูลจาก <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"การตั้งค่าหน้าต่างแว่นขยาย"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"แตะเพื่อเปิดฟีเจอร์การช่วยเหลือพิเศษ ปรับแต่งหรือแทนที่ปุ่มนี้ในการตั้งค่า\n\n"<annotation id="link">"ดูการตั้งค่า"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ย้ายปุ่มไปที่ขอบเพื่อซ่อนชั่วคราว"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"เลิกทำ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{นำทางลัด {label} รายการออกแล้ว}other{นำทางลัด # รายการออกแล้ว}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ย้ายไปด้านซ้ายบน"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ย้ายไปด้านขวาบน"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ย้ายไปด้านซ้ายล่าง"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ย้ายไปด้านขาวล่าง"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ย้ายไปที่ขอบและซ่อน"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ย้ายออกจากขอบและแสดง"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"นำออก"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"สลับ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ระบบควบคุมอุปกรณ์"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"เลือกแอปเพื่อเพิ่มตัวควบคุม"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"อินเทอร์เน็ตมือถือ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"เชื่อมต่อแล้ว"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"เชื่อมต่อแล้วชั่วคราว"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"การเชื่อมต่อไม่ดี"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"อินเทอร์เน็ตมือถือจะไม่เชื่อมต่ออัตโนมัติ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ไม่มีการเชื่อมต่อ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ไม่มีเครือข่ายอื่นๆ ที่พร้อมใช้งาน"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index f47b906..1451ab9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Hindi makilala ang mukha"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gumamit ng fingerprint"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Nakakonekta ang Bluetooth."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Hindi alam ang porsyento ng baterya."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Nakakonekta sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -303,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Available ang mikropono"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Available ang camera"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Available ang mikropono at camera"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Naka-on ang mikropono"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Naka-off ang mikropono"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Naka-enable para sa lahat ng app at serbisyo ang mikropono."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Naka-disable para sa lahat ng app at serbisyo ang access sa mikropono. Puwede mong i-enable ang access sa mikropono sa Mga Setting > Privacy > Mikropono."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Naka-disable para sa lahat ng app at serbisyo ang access sa mikropono. Puwede mo itong baguhin sa Mga Setting > Privacy > Mikropono."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Naka-on ang camera"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Naka-off ang camera"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Naka-enable para sa lahat ng app at serbisyo ang camera."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Naka-disable para sa lahat ng app at serbisyo ang access sa camera."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para gamitin ang button ng mikropono, i-enable ang access sa mikropono sa Mga Setting."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Buksan ang mga setting."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Iba pang device"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"I-toggle ang Overview"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Hindi ka maiistorbo ng mga tunog at pag-vibrate, maliban mula sa mga alarm, paalala, event, at tumatawag na tutukuyin mo. Maririnig mo pa rin ang kahit na anong piliin mong i-play kabilang ang mga musika, video, at laro."</string>
@@ -373,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang impormasyon."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Magpatuloy"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ibahagi o i-record ang isang app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Pamahalaan"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -727,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"I-off ang mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Hindi ka magkaka-access sa data o internet sa pamamagitan ng <xliff:g id="CARRIER">%s</xliff:g>. Available lang ang internet sa pamamagitan ng Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ang iyong carrier"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Bumalik sa <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Hindi awtomatikong magbabago ang mobile data base sa availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hindi, salamat na lang"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oo, lumipat"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Hindi ma-verify ng Mga Setting ang iyong tugon dahil may app na tumatakip sa isang kahilingan sa pagpapahintulot."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Payagan ang <xliff:g id="APP_0">%1$s</xliff:g> na ipakita ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Nakakabasa ito ng impormasyon mula sa <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -793,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Mga setting ng window ng magnifier"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"I-tap, buksan mga feature ng accessibility. I-customize o palitan button sa Mga Setting.\n\n"<annotation id="link">"Tingnan ang mga setting"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ilipat ang button sa gilid para pansamantala itong itago"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"I-undo"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} shortcut ang naalis}one{# shortcut ang naalis}other{# na shortcut ang naalis}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Ilipat sa kaliwa sa itaas"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Ilipat sa kanan sa itaas"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Ilipat sa kaliwa sa ibaba"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Ilipat sa kanan sa ibaba"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ilipat sa sulok at itago"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Alisin sa sulok at ipakita"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Alisin"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"i-toggle"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Mga kontrol ng device"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Pumili ng app para magdagdag ng mga kontrol"</string>
@@ -947,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Nakakonekta"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Pansamantalang nakakonekta"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Mahina ang koneksyon"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Hindi awtomatikong kokonekta ang mobile data"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Walang koneksyon"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Walang available na iba pang network"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 17dfa48..098091a 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Yüz tanınamadı"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bunun yerine parmak izi kullanın"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth bağlandı."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pil yüzdesi bilinmiyor."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ile bağlı."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kullanıcıları yönet"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Bitti"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Kapat"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Bağlı"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon kullanılabilir"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera kullanılabilir"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon ve kamera kullanılabilir"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon açık"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon kapalı"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon tüm uygulama ve hizmetler için etkinleştirildi."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofon erişimi tüm uygulama ve hizmetler için devre dışı bırakıldı. Mikrofon erişimini Ayarlar > Gizlilik > Mikrofon\'da etkinleştirebilirsiniz."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofon erişimi tüm uygulama ve hizmetler için devre dışı bırakıldı. Bunu Ayarlar > Gizlilik > Mikrofon\'da değiştirebilirsiniz."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera açıldı"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera devre dışı bırakıldı"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera tüm uygulama ve hizmetler için etkinleştirildi."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera erişimi tüm uygulama ve hizmetler için devre dışı bırakıldı."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofon düğmesini kullanmak için Ayarlar\'da mikrofon erişimini etkinleştirin."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ayarları aç."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Diğer cihaz"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Genel bakışı aç/kapat"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Alarmlar, hatırlatıcılar, etkinlikler ve sizin seçtiğiniz kişilerden gelen çağrılar dışında hiçbir ses ve titreşimle rahatsız edilmeyeceksiniz. O sırada çaldığınız müzik, seyrettiğiniz video ya da oynadığınız oyunların sesini duymaya devam edeceksiniz."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Devam"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Uygulamayı paylaşın veya kaydedin"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil veri kapatılsın mı?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> üzerinden veri veya internet erişiminiz olmayacak. İnternet yalnızca kablosuz bağlantı üzerinden kullanılabilecek."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatörünüz"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operatörüne geri dönülsün mü?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Uygunluk durumuna göre otomatik olarak mobil veriye geçilmez"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hayır, teşekkürler"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Evet, geçilsin"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Bir uygulama bir izin isteğinin anlaşılmasını engellediğinden, Ayarlar, yanıtınızı doğrulayamıyor."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> uygulamasının, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermesine izin verilsin mi?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasından bilgileri okuyabilir"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Büyüteç penceresi ayarları"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erişilebilirlik özelliklerini açmak için dokunun. Bu düğmeyi Ayarlar\'dan özelleştirin veya değiştirin.\n\n"<annotation id="link">"Ayarları göster"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düğmeyi geçici olarak gizlemek için kenara taşıyın"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri al"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} kısayol kaldırıldı}other{# kısayol kaldırıldı}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sol üste taşı"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sağ üste taşı"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sol alta taşı"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sağ alta taşı"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Kenara taşıyıp gizle"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Kenarın dışına taşıyıp göster"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Kaldır"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"değiştir"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Cihaz denetimleri"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Denetim eklemek için uygulama seçin"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil veri"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Bağlı"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Geçici olarak bağlandı"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Bağlantı zayıf"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil veri otomatik olarak bağlanmıyor"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yok"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Kullanılabilir başka ağ yok"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index ff1b594..ef05be6 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Обличчя не розпізнано"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скористайтеся відбитком"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth під’єднано."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Відсоток заряду акумулятора невідомий."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Підключено до <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Керувати користувачами"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрити"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Під’єднано"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Мікрофон доступний"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера доступна"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Мікрофон і камера доступні"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Мікрофон увімкнено"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Мікрофон вимкнено"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Мікрофон увімкнено для всіх додатків і сервісів."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Доступ до мікрофона вимкнено для всіх додатків і сервісів. Щоб надати доступ до мікрофона, виберіть \"Налаштування\" > \"Конфіденційність\" > \"Мікрофон\"."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Доступ до мікрофона вимкнено для всіх додатків і сервісів. Щоб змінити це, виберіть \"Налаштування\" > \"Конфіденційність\" > \"Мікрофон\"."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камеру ввімкнено"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камеру вимкнено"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камеру ввімкнено для всіх додатків і сервісів."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Доступ до камери вимкнено для всіх додатків і сервісів."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Щоб використовувати кнопку мікрофона, надайте доступ до мікрофона в налаштуваннях."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Відкрити налаштування"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Інший пристрій"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Увімкнути або вимкнути огляд"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Ви отримуватиме звукові та вібросигнали лише для вибраних будильників, нагадувань, подій і абонентів. Однак ви чутимете все, що захочете відтворити, зокрема музику, відео й ігри."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Коли ви показуєте, записуєте або транслюєте додаток, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продовжити"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Показувати або записувати додаток"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Вимкнути мобільний Інтернет?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Ви не матимете доступу до даних чи Інтернету через оператора <xliff:g id="CARRIER">%s</xliff:g>. Інтернет буде доступний лише через Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Перейти на <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Пристрій не перемикатиметься на мобільний Інтернет автоматично"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ні, дякую"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Так, перемикатися"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Не вдається підтвердити вашу відповідь у налаштуваннях, оскільки інший додаток заступає запит на дозвіл."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Дозволити додатку <xliff:g id="APP_0">%1$s</xliff:g> показувати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Має доступ до інформації з додатка <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Налаштування розміру лупи"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Кнопка спеціальних можливостей. Змініть або замініть її в Налаштуваннях.\n\n"<annotation id="link">"Переглянути налаштування"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Щоб тимчасово сховати кнопку, перемістіть її на край екрана"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Відмінити"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Функцію спеціальних можливостей \"{label}\" вилучено}one{# функцію спеціальних можливостей вилучено}few{# функції спеціальних можливостей вилучено}many{# функцій спеціальних можливостей вилучено}other{# функції спеціальних можливостей вилучено}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перемістити ліворуч угору"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перемістити праворуч угору"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перемістити ліворуч униз"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перемістити праворуч униз"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перемістити до краю, приховати"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Перемістити від краю, показати"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Вилучити"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"перемкнути"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Керування пристроями"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Виберіть, для якого додатка налаштувати елементи керування"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобільний трафік"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Підключено"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Тимчасово з’єднано"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Погане з’єднання"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобільний Інтернет не підключатиметься автоматично"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Немає з\'єднання"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Інші мережі недоступні"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 10e0e68..f1992a7 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"چہرے کی پہچان نہیں ہو سکی"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"اس کے بجائے فنگر پرنٹ استعمال کریں"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوٹوتھ مربوط ہے۔"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"بیٹری کی فیصد نامعلوم ہے۔"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> سے منسلک ہیں۔"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"صارفین کا نظم کریں"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ہو گیا"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"بند کریں"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"مربوط"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"مائیکروفون دستیاب ہے"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"کیمرا دستیاب ہے"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"مائیکروفون اور کیمرا دستیاب ہیں"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"دوسرا آلہ"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"مجموعی جائزہ ٹوگل کریں"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"الارمز، یاددہانیوں، ایونٹس اور آپ کے متعین کردہ کالرز کے علاوہ، آپ آوازوں اور وائبریشنز سے ڈسٹرب نہیں ہوں گے۔ موسیقی، ویڈیوز اور گیمز سمیت آپ ابھی بھی ہر وہ چیز سنیں گے جسے چلانے کا آپ انتخاب کرتے ہیں۔"</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"جاری رکھیں"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ایپ کا اشتراک یا ریکارڈ کریں"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"موبائل ڈیٹا آف کریں؟"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"آپ کو <xliff:g id="CARRIER">%s</xliff:g> کے ذریعے ڈیٹا یا انٹرنیٹ تک رسائی حاصل نہیں ہوگی۔ انٹرنیٹ صرف Wi-Fi کے ذریعے دستیاب ہوگا۔"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"آپ کا کریئر"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> پر واپس سوئچ کریں؟"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"دستیابی کی بنیاد پر موبائل ڈیٹا خودکار طور پر تبدیل نہیں ہوگا"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"نہیں شکریہ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ہاں، سوئچ کریں"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"چونکہ ایک ایپ اجازت کی درخواست کو مبہم کر رہی ہے، لہذا ترتیبات آپ کے جواب کی توثیق نہیں کر سکتی ہیں۔"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> کو <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانے کی اجازت دیں؟"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- یہ <xliff:g id="APP">%1$s</xliff:g> کی معلومات پڑھ سکتا ہے"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"میگنیفائر ونڈو کی ترتیبات"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ایکسیسبیلٹی خصوصیات کھولنے کے لیے تھپتھپائیں۔ ترتیبات میں اس بٹن کو حسب ضرورت بنائیں یا تبدیل کریں۔\n\n"<annotation id="link">"ترتیبات ملاحظہ کریں"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"عارضی طور پر بٹن کو چھپانے کے لئے اسے کنارے پر لے جائیں"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"کالعدم کریں"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} شارٹ کٹ ہٹا دیا گیا}other{# شارٹ کٹس ہٹا دیے گئے}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"اوپر بائیں جانب لے جائیں"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"اوپر دائیں جانب لے جائيں"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"نیچے بائیں جانب لے جائیں"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"نیچے دائیں جانب لے جائیں"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"EDGE پر لے جائیں اور چھپائیں"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"EDGE اور شو سے باہر منتقل کریں"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ہٹائیں"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ٹوگل کریں"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"آلہ کے کنٹرولز"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"کنٹرولز شامل کرنے کے لیے ایپ منتخب کریں"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"موبائل ڈیٹا"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g> / <xliff:g id="STATE">%1$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"منسلک ہے"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"عارضی طور پر منسلک ہے"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"کمزور کنکشن"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"موبائل ڈیٹا خودکار طور پر منسلک نہیں ہوگا"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"کوئی کنکشن نہیں"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"کوئی دوسرا نیٹ ورک دستیاب نہیں ہے"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 184044d..96e031a 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Yuz aniqlanmadi"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmoq izi orqali urining"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ulandi."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareya quvvati foizi nomaʼlum."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ulangan: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Foydalanuvchilarni boshqarish"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Tayyor"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Yopish"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Ulangan"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon mavjud"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera mavjud"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon va kamera mavjud"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon yoqildi"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon oʻchirildi"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon barcha ilovalar va xizmatlar uchun yoqilgan."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofon ruxsati barcha ilovalar va xizmatlar uchun oʻchirilgan. Sozlamalar > Maxfiylik > Mikrofon orqali mikrofon ruxsatini yoqishingiz mumkin."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofon ruxsati barcha ilovalar va xizmatlar uchun oʻchirilgan. Buni Sozlamalar > Maxfiylik > Mikrofon menyusida oʻzgartirishingiz mumkin."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera yoqildi"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera oʻchirildi"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera barcha ilovalar va xizmatlar uchun yoqilgan."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera ruxsati barcha ilovalar va xizmatlar uchun oʻchirilgan."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofon tugmasidan foydalanish uchun Sozlamalar orqali mikrofon ruxsatini yoqing."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Sozlamalarni ochish."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Boshqa qurilma"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Umumiy nazar rejimini almashtirish"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Turli ovoz va tebranishlar endi sizni bezovta qilmaydi. Biroq, signallar, eslatmalar, tadbirlar haqidagi bildirishnomalar va siz tanlagan abonentlardan kelgan chaqiruvlar bundan mustasno. Lekin, ijro etiladigan barcha narsalar, jumladan, musiqa, video va o‘yinlar ovozi eshitiladi."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Ulashish, yozib olish va translatsiya qilish vaqtida <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilovasi ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davom etish"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ilovada ulashish yoki yozib olish"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Tarix"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil internet uzilsinmi?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> orqali internetdan foydalana olmaysiz. Internet faqat Wi-Fi orqali ishlaydi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"aloqa operatoringiz"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> xizmati qaytarilsinmi?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobil internet mavjudligi asosida avtomatik almashtirilmaydi"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Kerak emas"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ha, almashtirilsin"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Ilova ruxsatnoma so‘roviga xalaqit qilayotgani tufayli, “Sozlamalar” ilovasi javobingizni tekshira olmaydi."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasiga <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatishga ruxsat berilsinmi?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– <xliff:g id="APP">%1$s</xliff:g> ma’lumotlarini o‘qiy oladi"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Lupa oynasi sozlamalari"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Maxsus imkoniyatlarni ochish uchun bosing Sozlamalardan moslay yoki almashtira olasiz.\n\n"<annotation id="link">"Sozlamalar"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Vaqtinchalik berkitish uchun tugmani qirra tomon suring"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Bekor qilish"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ta yorliq olindi}other{# ta yorliq olindi}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuqori chapga surish"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Yuqori oʻngga surish"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Quyi chapga surish"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Quyi oʻngga surish"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Chetiga olib borish va yashirish"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Chetidan qaytarish va koʻrsatish"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Olib tashlash"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"oʻzgartirish"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Qurilmalarni boshqarish"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Boshqaruv elementlarini kiritish uchun ilovani tanlang"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil internet"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ulandi"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Vaqtincha ulangan"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Aloqa beqaror"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil internetga avtomatik ulanmaydi"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Internetga ulanmagansiz"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Boshqa tarmoqlar mavjud emas"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 8ea3408..b253a91 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Không nhận ra khuôn mặt"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Hãy dùng vân tay"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Đã kết nối bluetooth."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Tỷ lệ phần trăm pin không xác định."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Đã kết nối với <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Quản lý người dùng"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Xong"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Đóng"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Đã kết nối"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micrô đang bật"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Máy ảnh đang bật"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micrô và máy ảnh đang bật"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Đã bật micrô"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Đã tắt micrô"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mọi ứng dụng và dịch vụ được phép sử dụng micrô."</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mọi ứng dụng và dịch vụ không có quyền truy cập vào micrô. Bạn có thể bật quyền truy cập vào micrô trong phần Cài đặt > Quyền riêng tư > Micrô."</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mọi ứng dụng và dịch vụ không có quyền truy cập vào micrô. Bạn có thể thay đổi chế độ này trong phần Cài đặt > Quyền riêng tư > Micrô."</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Đã bật máy ảnh"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Đã tắt máy ảnh"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Mọi ứng dụng và dịch vụ được phép sử dụng máy ảnh."</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Mọi ứng dụng và dịch vụ không có quyền truy cập vào máy ảnh."</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Để dùng nút micrô, hãy bật quyền truy cập vào micrô trong phần Cài đặt."</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Mở phần cài đặt."</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Thiết bị khác"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Bật/tắt chế độ xem Tổng quan"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Bạn sẽ không bị làm phiền bởi âm thanh và tiếng rung, ngoại trừ báo thức, lời nhắc, sự kiện và người gọi mà bạn chỉ định. Bạn sẽ vẫn nghe thấy mọi thứ bạn chọn phát, bao gồm nhạc, video và trò chơi."</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tiếp tục"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Chia sẻ hoặc ghi ứng dụng"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Lịch sử"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Tắt dữ liệu di động?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Bạn sẽ không có quyền sử dụng dữ liệu hoặc truy cập Internet thông qua chế độ <xliff:g id="CARRIER">%s</xliff:g>. Bạn chỉ có thể truy cập Internet thông qua Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"nhà mạng của bạn"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Chuyển về <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Dữ liệu di động sẽ không tự động chuyển dựa trên tình trạng phủ sóng"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Không, cảm ơn"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Có, hãy chuyển"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Vì ứng dụng đang che khuất yêu cầu cấp quyền nên Cài đặt không thể xác minh câu trả lời của bạn."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Cho phép <xliff:g id="APP_0">%1$s</xliff:g> hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Có thể đọc thông tin từ <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Chế độ cài đặt cửa sổ phóng to"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Nhấn để mở bộ tính năng hỗ trợ tiếp cận. Tuỳ chỉnh/thay thế nút này trong phần Cài đặt.\n\n"<annotation id="link">"Xem chế độ cài đặt"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Di chuyển nút sang cạnh để ẩn nút tạm thời"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Huỷ"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Đã xoá {label} lối tắt}other{Đã xoá # lối tắt}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Chuyển lên trên cùng bên trái"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Chuyển lên trên cùng bên phải"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Chuyển tới dưới cùng bên trái"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Chuyển tới dưới cùng bên phải"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Chuyển đến cạnh và ẩn"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Chuyển ra xa cạnh và hiển thị"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Xoá"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"bật/tắt"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Điều khiển thiết bị"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Chọn ứng dụng để thêm các tùy chọn điều khiển"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dữ liệu di động"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Đã kết nối"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tạm thời có kết nối"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Kết nối kém"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Dữ liệu di động sẽ không tự động kết nối"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Không có kết nối mạng"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Không có mạng nào khác"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 1cead42..64357a0 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"人脸识别失败"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"改用指纹"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"蓝牙已连接。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"电池电量百分比未知。"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已连接到<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"管理用户"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"完成"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"关闭"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"已连接"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"麦克风可用"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"摄像头可用"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"麦克风和摄像头可用"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"麦克风已开启"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"麦克风已关闭"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"已允许所有应用和服务访问麦克风。"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"已阻止所有应用和服务访问麦克风。您可依次前往“设置”>“隐私设置”>“麦克风”来启用麦克风访问权限。"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"已阻止所有应用和服务访问麦克风。您可依次前往“设置”>“隐私设置”>“麦克风”来更改此权限设置。"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"摄像头已开启"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"摄像头已关闭"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"已允许所有应用和服务访问摄像头。"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"已阻止所有应用和服务访问摄像头。"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"若要使用麦克风按钮,请在“设置”中启用麦克风访问权限。"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"打开设置。"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"其他设备"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切换概览"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"您将不会受到声音和振动的打扰(闹钟、提醒、活动和所指定来电者的相关提示音除外)。您依然可以听到您选择播放的任何内容(包括音乐、视频和游戏)的相关音效。"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"要关闭移动数据网络吗?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"您将无法通过<xliff:g id="CARRIER">%s</xliff:g>使用移动数据或互联网,只能通过 WLAN 连接到互联网。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的运营商"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"切换回 <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"移动流量不会根据可用性自动切换"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切换"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"由于某个应用遮挡了权限请求界面,因此“设置”应用无法验证您的回应。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"要允许“<xliff:g id="APP_0">%1$s</xliff:g>”显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块吗?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以读取“<xliff:g id="APP">%1$s</xliff:g>”中的信息"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大镜窗口设置"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"点按即可打开无障碍功能。您可在“设置”中自定义或更换此按钮。\n\n"<annotation id="link">"查看设置"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"将按钮移到边缘,即可暂时将其隐藏"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"撤消"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{已移除快捷方式 {label}}other{已移除 # 个快捷方式}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移至左上角"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移至右上角"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移至左下角"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移至右下角"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移至边缘并隐藏"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"移至边缘以外并显示"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"移除"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"开启/关闭"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"设备控制器"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"选择要添加控制器的应用"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"移动数据网络"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"已连接"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"暂时已连接"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"连接状况不佳"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"系统将不会自动连接到移动数据网络"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"无网络连接"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"没有其他可用网络"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index cb90614..ec9112c 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識面孔"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"電量百分比不明。"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"管理使用者"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"完成"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"關閉"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"已連線"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"可使用麥克風"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"可使用相機"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"可使用麥克風和相機"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"麥克風已開啟"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"麥克風已關閉"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"已為所有應用程式和服務啟用麥克風。"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"已停用所有應用程式和服務的麥克風存取權。您可以在 [設定] > [私隱] > [麥克風] 啟用麥克風存取權。"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"已停用所有應用程式和服務的麥克風存取權。您可以在 [設定] > [私隱] > [麥克風] 更改設定。"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"相機已開啟"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"相機已關閉"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"已為所有應用程式和服務啟用相機。"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"已停用所有應用程式和服務的相機存取權。"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"如要使用麥克風按鈕,請在「設定」中啟用麥克風存取權。"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"開啟設定。"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"其他裝置"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換概覽"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"您不會受到聲音和震動騷擾 (鬧鐘、提醒、活動和您指定的來電者鈴聲除外)。當您選擇播放音樂、影片和遊戲等,仍可以聽到該內容的聲音。"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉流動數據嗎?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"您無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用流動數據或互聯網。如要使用互聯網,您必須連接 Wi-Fi。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的流動網絡供應商"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"要切換回「<xliff:g id="CARRIER">%s</xliff:g>」嗎?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"流動數據不會根據可用性自動切換"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了,謝謝"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切換回 DDS 對話框"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式已阻擋權限要求畫面,因此「設定」應用程式無法驗證您的回應。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊嗎?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以讀取「<xliff:g id="APP">%1$s</xliff:g>」中的資料"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大鏡視窗設定"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"㩒一下就可以開無障礙功能。喺「設定」度自訂或者取代呢個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣即可暫時隱藏"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{已移除 {label} 個捷徑}other{已移除 # 個捷徑}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移去左上方"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移去右上方"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移到左下方"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移去右下方"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移到邊緣並隱藏"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"從邊緣移出並顯示"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"移除"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切換"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"選擇要新增控制項的應用程式"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"流動數據"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"已暫時連線"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"連線速度欠佳"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"不會自動連線至流動數據"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有連線"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網絡"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index ccad340..0f82675 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識臉孔"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"電池電量不明。"</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"管理使用者"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"完成"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"關閉"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"已連線"</string>
@@ -304,6 +305,17 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"可使用麥克風"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"可使用相機"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"可使用麥克風和相機"</string>
+ <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"麥克風已開啟"</string>
+ <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"麥克風已關閉"</string>
+ <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"所有應用程式和服務的麥克風存取權皆已啟用。"</string>
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"所有應用程式和服務的麥克風存取權皆已停用。如要啟用麥克風存取權,請依序前往「設定」>「隱私權」>「麥克風」。"</string>
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"所有應用程式和服務的麥克風存取權皆已停用。如要變更這項設定,請依序前往「設定」>「隱私權」>「麥克風」。"</string>
+ <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"相機已開啟"</string>
+ <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"相機已關閉"</string>
+ <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"所有應用程式和服務的相機存取權皆已啟用。"</string>
+ <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"所有應用程式和服務的相機存取權皆已停用。"</string>
+ <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"如要使用麥克風按鈕,請前往「設定」啟用麥克風存取權。"</string>
+ <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"開啟設定。"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"其他裝置"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換總覽"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"裝置不會發出音效或震動造成干擾,但是會保留與鬧鐘、提醒、活動和指定來電者有關的設定。如果你選擇播放音樂、影片和遊戲等內容,還是可以聽見相關音訊。"</string>
@@ -374,6 +386,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
@@ -728,14 +750,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉行動數據嗎?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"你將無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用行動數據或網際網路。你只能透過 Wi-Fi 使用網際網路。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"你的電信業者"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"要切換回「<xliff:g id="CARRIER">%s</xliff:g>」嗎?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"行動數據不會依據可用性自動切換"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了,謝謝"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切換回 DDS 對話方塊"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式覆蓋了權限要求畫面,因此「設定」應用程式無法驗證你的回應。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊嗎?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 它可以讀取「<xliff:g id="APP">%1$s</xliff:g>」的資訊"</string>
@@ -794,18 +812,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大鏡視窗設定"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"輕觸即可開啟無障礙功能。你可以前往「設定」自訂或更換這個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣處即可暫時隱藏"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{已移除 {label} 個捷徑}other{已移除 # 個捷徑}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移到左上方"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移到右上方"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移到左下方"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移到右下方"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移到邊緣並隱藏"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"從邊緣移出並顯示"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"移除"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切換"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"選擇應用程式以新增控制項"</string>
@@ -948,10 +963,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"行動數據"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"已暫時建立連線"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"連線品質不佳"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"系統將不會自動使用行動數據連線"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有網路連線"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網路"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index b346494..061592a 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -168,6 +168,8 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ayikwazi ukubona ubuso"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kunalokho sebenzisa isigxivizo somunwe"</string>
+ <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ixhunyiwe"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Iphesenti lebhethri alaziwa."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Xhuma ku-<xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -248,8 +250,7 @@
<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>
- <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
- <skip />
+ <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Phatha abasebenzisi"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Kwenziwe"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Vala"</string>
<string name="quick_settings_connected" msgid="3873605509184830379">"Ixhunyiwe"</string>
@@ -304,6 +305,28 @@
<string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Imakrofoni iyatholakala"</string>
<string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Ikhamera iyatholakala"</string>
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Imakrofoni nekhamera kuyatholakala"</string>
+ <!-- no translation found for sensor_privacy_mic_turned_on_dialog_title (6348853159838376513) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_turned_off_dialog_title (5760464281790732849) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_unblocked_dialog_content (4889961886199270224) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_no_exception_dialog_content (5864898470772965394) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_mic_blocked_with_exception_dialog_content (810289713700437896) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_on_dialog_title (8039095295100075952) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_turned_off_dialog_title (1936603903120742696) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_unblocked_dialog_content (7847190103011782278) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_camera_blocked_dialog_content (3182428709314874616) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_htt_blocked_dialog_content (3333321592997666441) -->
+ <skip />
+ <!-- no translation found for sensor_privacy_dialog_open_settings (1503088305279285048) -->
+ <skip />
<string name="media_seamless_other_device" msgid="4654849800789196737">"Enye idivayisi"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Guqula ukubuka konke"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"Ngeke uphazanyiswe imisindo nokudlidliza, ngaphandle kusukela kuma-alamu, izikhumbuzi, imicimbi, nabafonayo obacacisayo. Usazozwa noma yini okhetha ukuyidlala okufaka umculo, amavidiyo, namageyimu."</string>
@@ -374,6 +397,16 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Uma wabelana, urekhoda, noma usakaza i-app, i-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inokufinyelela kunoma yini eboniswayo noma edlalwayo kuleyo app. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Qhubeka"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Yabelana noma rekhoda i-app"</string>
+ <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
+ <skip />
+ <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
+ <skip />
+ <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
+ <skip />
<string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Phatha"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Umlando"</string>
@@ -728,14 +761,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vala idatha yeselula?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Ngeke ube nokufinyelela kudatha noma ku-inthanethi nge-<xliff:g id="CARRIER">%s</xliff:g>. I-inthanethi izotholakala kuphela nge-Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"inkampani yakho yenethiwekhi"</string>
- <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
- <skip />
- <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
- <skip />
- <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
- <skip />
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Shintshela emuva ku-<xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Idatha yeselula ngeke ishintshe ngokuzenzakalelayo ngokusekelwe ekutholakaleni"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Cha ngiyabonga"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yebo, shintsha"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Ngoba uhlelo lokusebenza lusitha isicelo semvume, Izilungiselelo azikwazi ukuqinisekisa impendulo yakho."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vumela i-<xliff:g id="APP_0">%1$s</xliff:g> ukuthi ibonise izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Ingafunda ulwazi kusukela ku-<xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -794,18 +823,15 @@
<string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Amasethingi ewindi lesikhulisi"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Thepha ukuze uvule izakhi zokufinyelela. Enza ngendlela oyifisayo noma shintsha le nkinobho Kumasethingi.\n\n"<annotation id="link">"Buka amasethingi"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Hambisa inkinobho onqenqemeni ukuze uyifihle okwesikhashana"</string>
- <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
- <skip />
- <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
- <skip />
+ <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Hlehlisa"</string>
+ <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Isinqamuleli se-{label} sisusiwe}one{Izinqamuleli ezingu-# zikhishiwe}other{Izinqamuleli ezingu-# zikhishiwe}}"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Hamba phezulu kwesokunxele"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Hamba phezulu ngakwesokudla"</string>
<string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Hamba phansi ngakwesokunxele"</string>
<string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Hamba phansi ngakwesokudla"</string>
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Hamba onqenqemeni ufihle"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Phuma onqenqemeni ubonise"</string>
- <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
- <skip />
+ <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Susa"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"guqula"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Izilawuli zezinsiza"</string>
<string name="controls_providers_title" msgid="6879775889857085056">"Khetha uhlelo lokusebenza ukwengeza izilawuli"</string>
@@ -948,10 +974,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Idatha yeselula"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ixhunyiwe"</string>
- <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
- <skip />
- <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
- <skip />
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ixhume okwesikhashana"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Uxhumo olungeluhle"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Idatha yeselula ngeke ikwazi ukuxhuma ngokuzenzekelayo"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Alukho uxhumano"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Awekho amanye amanethiwekhi atholakalayo"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index df0659d..44ba3f6 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -204,5 +204,10 @@
<attr name="passwordTextAppearance" format="reference" />
<attr name="errorTextAppearance" format="reference"/>
</declare-styleable>
+
+ <declare-styleable name="LogAccessPermissionGrantDialog">
+ <attr name="permissionGrantButtonTopStyle" format="reference"/>
+ <attr name="permissionGrantButtonBottomStyle" format="reference"/>
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 8221d78..04fc4b8 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -25,6 +25,9 @@
<!-- Whether to enable clipping on Quick Settings -->
<bool name="qs_enable_clipping">true</bool>
+ <!-- Whether to enable clipping on Notification Views -->
+ <bool name="notification_enable_clipping">true</bool>
+
<!-- Whether to enable transparent background for notification scrims -->
<bool name="notification_scrim_transparent">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 55b59b6..6b4bea1 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -172,6 +172,10 @@
<color name="accessibility_magnifier_bg">#FCFCFC</color>
<color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
<color name="accessibility_magnifier_icon_color">#252525</color>
+ <color name="accessibility_window_magnifier_button_bg">#0680FD</color>
+ <color name="accessibility_window_magnifier_icon_color">#FAFAFA</color>
+ <color name="accessibility_window_magnifier_button_bg_stroke">#252525</color>
+ <color name="accessibility_window_magnifier_corner_view_color">#0680FD</color>
<!-- Volume dialog colors -->
<color name="volume_dialog_background_color">@android:color/transparent</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ce9829b..b8e2caf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -285,6 +285,9 @@
<!-- Whether to show the full screen user switcher. -->
<bool name="config_enableFullscreenUserSwitcher">false</bool>
+ <!-- Determines whether the shell features all run on another thread. -->
+ <bool name="config_enableShellMainThread">true</bool>
+
<!-- SystemUIFactory component -->
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>
@@ -485,6 +488,12 @@
<!-- Whether to show a severe low battery dialog. -->
<bool name="config_severe_battery_dialog">false</bool>
+ <!-- A path representing a shield. Will sometimes be displayed with the battery icon when
+ needed. This path is a 10px wide and 13px tall. -->
+ <string name="config_batterymeterShieldPath" translatable="false">
+ M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
+ </string>
+
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
@@ -737,12 +746,35 @@
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
+ <!-- The duration in milliseconds of the y-translation animation when waking up from
+ the dream -->
+ <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
+ <!-- The delay in milliseconds of the y-translation animation when waking up from
+ the dream for the complications at the bottom of the screen -->
+ <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
+ <!-- The delay in milliseconds of the y-translation animation when waking up from
+ the dream for the complications at the top of the screen -->
+ <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
+ <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
+ <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
+ <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+ complications at the top of the screen -->
+ <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
+ <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+ complications at the bottom of the screen -->
+ <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
+ <!-- The duration in milliseconds of the blur animation when waking up from
+ the dream -->
+ <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
+
<integer name="complicationFadeOutMs">500</integer>
<integer name="complicationFadeInMs">500</integer>
<integer name="complicationRestoreMs">1000</integer>
+ <integer name="complicationFadeOutDelayMs">200</integer>
+
<!-- Duration in milliseconds of the dream in un-blur animation. -->
<integer name="config_dreamOverlayInBlurDurationMs">249</integer>
<!-- Delay in milliseconds of the dream in un-blur animation. -->
@@ -774,28 +806,27 @@
<item>com.android.systemui</item>
</string-array>
- <!-- The thresholds which determine the color used by the AQI dream overlay.
- NOTE: This must always be kept sorted from low to high -->
- <integer-array name="config_dreamAqiThresholds">
- <item>-1</item>
- <item>50</item>
- <item>100</item>
- <item>150</item>
- <item>200</item>
- <item>300</item>
- </integer-array>
-
- <!-- The color values which correspond to the thresholds above -->
- <integer-array name="config_dreamAqiColorValues">
- <item>@color/dream_overlay_aqi_good</item>
- <item>@color/dream_overlay_aqi_moderate</item>
- <item>@color/dream_overlay_aqi_unhealthy_sensitive</item>
- <item>@color/dream_overlay_aqi_unhealthy</item>
- <item>@color/dream_overlay_aqi_very_unhealthy</item>
- <item>@color/dream_overlay_aqi_hazardous</item>
- </integer-array>
-
<!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
is available. If false, UI will never show regardless of tethering availability" -->
<bool name="config_show_wifi_tethering">true</bool>
+
+ <!-- A collection of "slots" for placing quick affordance actions on the lock screen when the
+ device is locked. Each item is a string consisting of two parts, separated by the ':' character.
+ The first part is the unique ID for the slot, it is not a human-visible name, but should still
+ be unique across all slots specified. The second part is the capacity and must be a positive
+ integer; this is how many quick affordance actions that user is allowed to add to the slot. -->
+ <string-array name="config_keyguardQuickAffordanceSlots" translatable="false">
+ <item>bottom_start:1</item>
+ <item>bottom_end:1</item>
+ </string-array>
+
+ <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
+ string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
+ separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
+ default is displayed by System UI as long as the user hasn't made a different choice for that
+ slot. If the user did make a choice, even if the choice is the "None" option, the default is
+ ignored. -->
+ <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+ </string-array>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f02f29a..67e2248 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -105,6 +105,12 @@
so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
<dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+ <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+ @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
+ the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
+ bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
+ <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+
<!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen>
@@ -407,7 +413,7 @@
<dimen name="match_parent">-1px</dimen>
<!-- Height of status bar in split shade mode - visible only on large screens -->
- <dimen name="large_screen_shade_header_height">@*android:dimen/quick_qs_offset_height</dimen>
+ <dimen name="large_screen_shade_header_height">48dp</dimen>
<dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
<dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
@@ -762,7 +768,7 @@
<dimen name="keyguard_lock_padding">20dp</dimen>
<dimen name="keyguard_indication_margin_bottom">32dp</dimen>
- <dimen name="lock_icon_margin_bottom">110dp</dimen>
+ <dimen name="lock_icon_margin_bottom">74dp</dimen>
<dimen name="ambient_indication_margin_bottom">71dp</dimen>
@@ -1335,6 +1341,7 @@
<dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
<dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen>
<dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
+ <dimen name="accessibility_floating_menu_ime_shifting_space">48dp</dimen>
<dimen name="accessibility_floating_menu_message_container_horizontal_padding">15dp</dimen>
<dimen name="accessibility_floating_menu_message_text_vertical_padding">8dp</dimen>
@@ -1416,6 +1423,11 @@
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+ <!-- Status bar user chip -->
+ <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
+ <dimen name="status_bar_user_chip_end_margin">12dp</dimen>
+ <dimen name="status_bar_user_chip_text_size">12sp</dimen>
+
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_max_height">662dp</dimen>
<!-- The height of the WiFi network in Internet panel. -->
@@ -1496,10 +1508,12 @@
<!-- Dream overlay complications related dimensions -->
<dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen>
+ <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
<dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
<dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
<dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
<dimen name="dream_overlay_complication_shadow_padding">2dp</dimen>
+ <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
<!-- The position of the end guide, which dream overlay complications can align their start with
if their end is aligned with the parent end. Represented as the percentage over from the
@@ -1544,6 +1558,7 @@
<dimen name="dream_overlay_complication_margin">0dp</dimen>
<dimen name="dream_overlay_y_offset">80dp</dimen>
+ <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
<dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
<dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 4fd25a9..2b6ab30 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -195,5 +195,7 @@
<item type="id" name="pm_lite"/>
<item type="id" name="settings_button_container"/>
+ <item type="id" name="log_access_dialog_allow_button" />
+ <item type="id" name="log_access_dialog_deny_button" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cbcfbf8..9eafdb9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -142,7 +142,7 @@
<string name="usb_debugging_secondary_user_title">USB debugging not allowed</string>
<!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
- <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
+ <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user.</string>
<!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=80] -->
<string name="hdmi_cec_set_menu_language_title">Do you want to change the system language to <xliff:g id="language" example="German">%1$s</xliff:g>?</string>
@@ -172,7 +172,7 @@
<string name="wifi_debugging_secondary_user_title">Wireless debugging not allowed</string>
<!-- Message of notification shown when trying to enable wireless debugging but a secondary user is the current foreground user. [CHAR LIMIT=NONE] -->
- <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to the primary user.</string>
+ <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to an admin user.</string>
<!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
<string name="usb_contaminant_title">USB port disabled</string>
@@ -235,6 +235,8 @@
<string name="screenshot_left_boundary_pct">Left boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
+ <!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
+ <string name="screenshot_work_profile_notification" translatable="false">Work screenshots are saved in the work <xliff:g id="app" example="Files">%1$s</xliff:g> app</string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_name">Screen Recorder</string>
@@ -314,7 +316,7 @@
<!-- Content description of the QR Code scanner for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_qr_code_scanner_button">QR Code Scanner</string>
<!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_unlock_button">Unlock</string>
+ <string name="accessibility_unlock_button">Unlocked</string>
<!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_lock_icon">Device locked</string>
<!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -403,8 +405,8 @@
<string name="keyguard_face_failed">Can\u2019t recognize face</string>
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
- <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] -->
- <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
+ <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=59] -->
+ <string name="keyguard_face_unlock_unavailable">Face Unlock unavailable</string>
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
@@ -437,11 +439,17 @@
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+ <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g></string>
<!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
+ <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent, charging paused for battery protection.</string>
+
+ <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g>, charging paused for battery protection.</string>
+
<!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
<string name="accessibility_overflow_action">See all notifications</string>
@@ -1367,7 +1375,7 @@
<string name="wallet_lockscreen_settings_label">Lock screen settings</string>
<!-- QR Code Scanner label, title [CHAR LIMIT=32] -->
- <string name="qr_code_scanner_title">Scan QR code</string>
+ <string name="qr_code_scanner_title">QR code scanner</string>
<!-- Name of the work status bar icon. -->
<string name="status_bar_work">Work profile</string>
@@ -1988,6 +1996,9 @@
<!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] -->
<string name="lockscreen_none">None</string>
+ <!-- ClockId to use when none is set by user -->
+ <string name="lockscreen_clock_id_fallback" translatable="false">DEFAULT</string>
+
<!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] -->
<string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string>
@@ -2229,6 +2240,8 @@
<string name="magnification_mode_switch_state_window">Magnify part of screen</string>
<!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_click_label">Switch</string>
+ <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
+ <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
<!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
<string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
@@ -2770,4 +2783,19 @@
<!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
<string name="dream_time_complication_24_hr_time_format">kk:mm</string>
+
+ <!-- Title for the log access confirmation dialog. [CHAR LIMIT=NONE] -->
+ <string name="log_access_confirmation_title">Allow <xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> to access all device logs?</string>
+ <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=40] -->
+ <string name="log_access_confirmation_allow">Allow one-time access</string>
+ <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+ <string name="log_access_confirmation_deny">Don\u2019t allow</string>
+
+ <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+ <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+ \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
+ </string>
+
+ <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+ <string name="log_access_confirmation_learn_more" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e76887b..fe4f639 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -23,6 +23,12 @@
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
+ <style name="TextAppearance.StatusBar.UserChip" parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <item name="android:textSize">@dimen/status_bar_user_chip_text_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textColor">@color/status_bar_clock_color</item>
+ </style>
+
<style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar">
<item name="android:textColor">?android:attr/textColorTertiary</item>
</style>
@@ -1091,7 +1097,7 @@
<item name="android:orientation">horizontal</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
- <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:background">@drawable/internet_dialog_selected_effect</item>
</style>
<style name="InternetDialog.NetworkTitle">
@@ -1274,4 +1280,45 @@
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
<item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
</style>
+
+
+ <!-- The style for log access consent dialog -->
+ <style name="LogAccessDialogTheme" parent="@style/Theme.SystemUI.Dialog.Alert">
+ <item name="permissionGrantButtonTopStyle">@style/PermissionGrantButtonTop</item>
+ <item name="permissionGrantButtonBottomStyle">@style/PermissionGrantButtonBottom</item>
+ </style>
+
+ <style name="AllowLogAccess">
+ <item name="android:textSize">24sp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
+
+ <style name="PrimaryAllowLogAccess">
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
+
+ <style name="PermissionGrantButtonTextAppearance">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@android:color/system_neutral1_900</item>
+ </style>
+
+ <style name="PermissionGrantButtonTop"
+ parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
+ <item name="android:layout_width">332dp</item>
+ <item name="android:layout_height">56dp</item>
+ <item name="android:layout_marginTop">2dp</item>
+ <item name="android:layout_marginBottom">2dp</item>
+ <item name="android:background">@drawable/grant_permissions_buttons_top</item>
+ </style>
+
+ <style name="PermissionGrantButtonBottom"
+ parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
+ <item name="android:layout_width">332dp</item>
+ <item name="android:layout_height">56dp</item>
+ <item name="android:layout_marginTop">2dp</item>
+ <item name="android:layout_marginBottom">2dp</item>
+ <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index cdbf8ab..06d425c 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -107,7 +107,7 @@
android:id="@+id/privacy_container">
<Layout
android:layout_width="wrap_content"
- android:layout_height="0dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 148e5ec..1eb621e 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -44,6 +44,16 @@
app:layout_constraintTop_toTopOf="@+id/album_art"
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+ <!-- Turbulence noise must have the same constraint as the album art. -->
+ <Constraint
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_collapsed"
+ app:layout_constraintStart_toStartOf="@+id/album_art"
+ app:layout_constraintEnd_toEndOf="@+id/album_art"
+ app:layout_constraintTop_toTopOf="@+id/album_art"
+ app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index ac484d7..64c2ef1 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -37,6 +37,16 @@
app:layout_constraintTop_toTopOf="@+id/album_art"
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+ <!-- Turbulence noise must have the same constraint as the album art. -->
+ <Constraint
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ app:layout_constraintStart_toStartOf="@+id/album_art"
+ app:layout_constraintEnd_toEndOf="@+id/album_art"
+ app:layout_constraintTop_toTopOf="@+id/album_art"
+ app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 88b4f43..5d3650c 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -25,7 +25,7 @@
android:id="@+id/clock">
<Layout
android:layout_width="wrap_content"
- android:layout_height="0dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="@id/begin_guide"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -42,7 +42,7 @@
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
android:layout_marginStart="8dp"
app:layout_constrainedWidth="true"
@@ -57,14 +57,16 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
@@ -80,12 +82,16 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
<Constraint
android:id="@+id/carrier_group">
<Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
@@ -98,7 +104,7 @@
android:id="@+id/privacy_container">
<Layout
android:layout_width="wrap_content"
- android:layout_height="0dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 8248fcd..982c422 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -22,50 +22,104 @@
>
<Constraint
+ android:id="@+id/privacy_container">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/carrier_group"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
android:id="@+id/clock">
<Layout
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintBottom_toBottomOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
+ <Transform
+ android:scaleX="2.57"
+ android:scaleY="2.57"
+ />
</Constraint>
<Constraint
android:id="@+id/date">
<Layout
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/clock"
+ app:layout_constraintEnd_toStartOf="@id/space"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
- />
- <Motion
- app:motionStagger="0.5"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
</Constraint>
<Constraint
android:id="@+id/carrier_group">
- <CustomAttribute
- app:attributeName="alpha"
- app:customFloatValue="1"
- />
+ <Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintStart_toEndOf="@id/clock"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ />
+ <PropertySet
+ android:alpha="1"
+ />
</Constraint>
<Constraint
- android:id="@+id/privacy_container">
+ android:id="@+id/statusIcons">
<Layout
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintStart_toEndOf="@id/space"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/batteryRemainingIcon">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="@id/date"
- />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ />
+ </Constraint>
+
+
+ <Constraint
+ android:id="@id/space">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/statusIcons"
+ />
</Constraint>
</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
deleted file mode 100644
index d8a4e77..0000000
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/qs_header_constraint"
->
-
- <Constraint
- android:id="@+id/privacy_container">
- <Layout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintEnd_toEndOf="@id/end_guide"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="1"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/clock">
- <Layout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/privacy_container"
- app:layout_constraintBottom_toBottomOf="@id/carrier_group"
- app:layout_constraintEnd_toStartOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="0"
- />
- <Transform
- android:scaleX="2.57"
- android:scaleY="2.57"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/date">
- <Layout
- android:layout_width="0dp"
- android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/space"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/carrier_group">
- <Layout
- app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
- android:minHeight="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintWidth_min="48dp"
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintStart_toEndOf="@id/clock"
- app:layout_constraintTop_toBottomOf="@id/privacy_container"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
- />
- <PropertySet
- android:alpha="1"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/statusIcons">
- <Layout
- android:layout_width="0dp"
- android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
- app:layout_constraintStart_toEndOf="@id/space"
- app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
- app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/batteryRemainingIcon">
- <Layout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toEndOf="@id/statusIcons"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- />
- </Constraint>
-
-
- <Constraint
- android:id="@id/space">
- <Layout
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintStart_toEndOf="@id/date"
- app:layout_constraintEnd_toStartOf="@id/statusIcons"
- />
- </Constraint>
-</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 0b0595f..36ac1ff 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,6 +34,7 @@
import platform.test.screenshot.DeviceEmulationRule
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.PathConfig
import platform.test.screenshot.ScreenshotTestRule
import platform.test.screenshot.getEmulatedDevicePathConfig
import platform.test.screenshot.matchers.BitmapMatcher
@@ -41,13 +42,19 @@
/** A rule for View screenshot diff unit tests. */
class ViewScreenshotTestRule(
emulationSpec: DeviceEmulationSpec,
- private val matcher: BitmapMatcher = UnitTestBitmapMatcher
+ private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
+ pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
+ assetsPathRelativeToRepo: String = ""
) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ if (assetsPathRelativeToRepo.isBlank()) {
+ SystemUIGoldenImagePathManager(pathConfig)
+ } else {
+ SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo)
+ }
)
private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
private val delegateRule =
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index f8f4d4a..8a0fca0 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -52,7 +52,6 @@
"SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
- "gson",
"androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.lifecycle_lifecycle-viewmodel-ktx",
"androidx.recyclerview_recyclerview",
@@ -64,9 +63,6 @@
resource_dirs: [
"res",
],
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
kotlincflags: ["-Xjvm-default=enable"],
diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags
deleted file mode 100644
index 5eda045..0000000
--- a/packages/SystemUI/shared/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
-# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry
--keepattributes Signature
--keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index 96a5840..f3aeaef 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -20,12 +20,6 @@
-->
<resources>
- <declare-styleable name="AnimatableClockView">
- <attr name="dozeWeight" format="integer" />
- <attr name="lockScreenWeight" format="integer" />
- <attr name="chargeAnimationDelay" format="integer" />
- </declare-styleable>
-
<declare-styleable name="DoubleShadowAttrDeclare">
<attr name="keyShadowBlur" format="dimension" />
<attr name="keyShadowOffsetX" format="dimension" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 933e586..196f7f0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -22,9 +22,19 @@
import android.os.Parcel
import android.os.Parcelable
+/**
+ * Base interface for flags that can change value on a running device.
+ * @property id unique id to help identify this flag. Must be unique. This will be removed soon.
+ * @property teamfood Set to true to include this flag as part of the teamfood flag. This will
+ * be removed soon.
+ * @property name Used for server-side flagging where appropriate. Also used for display. No spaces.
+ * @property namespace The server-side namespace that this flag lives under.
+ */
interface Flag<T> {
val id: Int
val teamfood: Boolean
+ val name: String
+ val namespace: String
}
interface ParcelableFlag<T> : Flag<T>, Parcelable {
@@ -38,13 +48,10 @@
}
interface DeviceConfigFlag<T> : Flag<T> {
- val name: String
- val namespace: String
val default: T
}
interface SysPropFlag<T> : Flag<T> {
- val name: String
val default: T
}
@@ -56,6 +63,8 @@
// Consider using the "parcelize" kotlin library.
abstract class BooleanFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val default: Boolean = false,
override val teamfood: Boolean = false,
override val overridden: Boolean = false
@@ -71,6 +80,8 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
+ name = parcel.readString(),
+ namespace = parcel.readString(),
default = parcel.readBoolean(),
teamfood = parcel.readBoolean(),
overridden = parcel.readBoolean()
@@ -78,6 +89,8 @@
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
+ parcel.writeString(name)
+ parcel.writeString(namespace)
parcel.writeBoolean(default)
parcel.writeBoolean(teamfood)
parcel.writeBoolean(overridden)
@@ -91,20 +104,24 @@
*/
data class UnreleasedFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val teamfood: Boolean = false,
override val overridden: Boolean = false
-) : BooleanFlag(id, false, teamfood, overridden)
+) : BooleanFlag(id, name, namespace, false, teamfood, overridden)
/**
- * A Flag that is is true by default.
+ * A Flag that is true by default.
*
* It can be changed or overridden in any build, meaning it can be turned off if needed.
*/
data class ReleasedFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val teamfood: Boolean = false,
override val overridden: Boolean = false
-) : BooleanFlag(id, true, teamfood, overridden)
+) : BooleanFlag(id, name, namespace, true, teamfood, overridden)
/**
* A Flag that reads its default values from a resource overlay instead of code.
@@ -113,6 +130,8 @@
*/
data class ResourceBooleanFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
@BoolRes override val resourceId: Int,
override val teamfood: Boolean = false
) : ResourceFlag<Boolean>
@@ -142,7 +161,8 @@
data class SysPropBooleanFlag constructor(
override val id: Int,
override val name: String,
- override val default: Boolean = false
+ override val namespace: String,
+ override val default: Boolean = false,
) : SysPropFlag<Boolean> {
// TODO(b/223379190): Teamfood not supported for sysprop flags yet.
override val teamfood: Boolean = false
@@ -150,6 +170,8 @@
data class StringFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val default: String = "",
override val teamfood: Boolean = false,
override val overridden: Boolean = false
@@ -164,23 +186,31 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
+ name = parcel.readString(),
+ namespace = parcel.readString(),
default = parcel.readString() ?: ""
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
+ parcel.writeString(name)
+ parcel.writeString(namespace)
parcel.writeString(default)
}
}
data class ResourceStringFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
@StringRes override val resourceId: Int,
override val teamfood: Boolean = false
) : ResourceFlag<String>
data class IntFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val default: Int = 0,
override val teamfood: Boolean = false,
override val overridden: Boolean = false
@@ -196,17 +226,23 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
+ name = parcel.readString(),
+ namespace = parcel.readString(),
default = parcel.readInt()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
+ parcel.writeString(name)
+ parcel.writeString(namespace)
parcel.writeInt(default)
}
}
data class ResourceIntFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
@IntegerRes override val resourceId: Int,
override val teamfood: Boolean = false
) : ResourceFlag<Int>
@@ -215,6 +251,8 @@
override val id: Int,
override val default: Long = 0,
override val teamfood: Boolean = false,
+ override val name: String,
+ override val namespace: String,
override val overridden: Boolean = false
) : ParcelableFlag<Long> {
@@ -228,17 +266,23 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
+ name = parcel.readString(),
+ namespace = parcel.readString(),
default = parcel.readLong()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
+ parcel.writeString(name)
+ parcel.writeString(namespace)
parcel.writeLong(default)
}
}
data class FloatFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val default: Float = 0f,
override val teamfood: Boolean = false,
override val overridden: Boolean = false
@@ -254,23 +298,31 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
+ name = parcel.readString(),
+ namespace = parcel.readString(),
default = parcel.readFloat()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
+ parcel.writeString(name)
+ parcel.writeString(namespace)
parcel.writeFloat(default)
}
}
data class ResourceFloatFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val resourceId: Int,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
) : ResourceFlag<Int>
data class DoubleFlag constructor(
override val id: Int,
+ override val name: String,
+ override val namespace: String,
override val default: Double = 0.0,
override val teamfood: Boolean = false,
override val overridden: Boolean = false
@@ -286,11 +338,15 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
+ name = parcel.readString(),
+ namespace = parcel.readString(),
default = parcel.readDouble()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
+ parcel.writeString(name)
+ parcel.writeString(namespace)
parcel.writeDouble(default)
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
new file mode 100644
index 0000000..71469a3
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.shared.keyguard.data.content
+
+import android.content.ContentResolver
+import android.net.Uri
+
+/** Contract definitions for querying content about keyguard quick affordances. */
+object KeyguardQuickAffordanceProviderContract {
+
+ const val AUTHORITY = "com.android.systemui.keyguard.quickaffordance"
+ const val PERMISSION = "android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+
+ private val BASE_URI: Uri =
+ Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build()
+
+ /**
+ * Table for slots.
+ *
+ * Slots are positions where affordances can be placed on the lock screen. Affordances that are
+ * placed on slots are said to be "selected". The system supports the idea of multiple
+ * affordances per slot, though the implementation may limit the number of affordances on each
+ * slot.
+ *
+ * Supported operations:
+ * - Query - to know which slots are available, query the [SlotTable.URI] [Uri]. The result set
+ * will contain rows with the [SlotTable.Columns] columns.
+ */
+ object SlotTable {
+ const val TABLE_NAME = "slots"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ object Columns {
+ /** String. Unique ID for this slot. */
+ const val ID = "id"
+ /** Integer. The maximum number of affordances that can be placed in the slot. */
+ const val CAPACITY = "capacity"
+ }
+ }
+
+ /**
+ * Table for affordances.
+ *
+ * Affordances are actions/buttons that the user can execute. They are placed on slots on the
+ * lock screen.
+ *
+ * Supported operations:
+ * - Query - to know about all the affordances that are available on the device, regardless of
+ * which ones are currently selected, query the [AffordanceTable.URI] [Uri]. The result set will
+ * contain rows, each with the columns specified in [AffordanceTable.Columns].
+ */
+ object AffordanceTable {
+ const val TABLE_NAME = "affordances"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ object Columns {
+ /** String. Unique ID for this affordance. */
+ const val ID = "id"
+ /** String. User-visible name for this affordance. */
+ const val NAME = "name"
+ /**
+ * Integer. Resource ID for the drawable to load for this affordance. This is a resource
+ * ID from the system UI package.
+ */
+ const val ICON = "icon"
+ }
+ }
+
+ /**
+ * Table for selections.
+ *
+ * Selections are pairs of slot and affordance IDs.
+ *
+ * Supported operations:
+ * - Insert - to insert an affordance and place it in a slot, insert values for the columns into
+ * the [SelectionTable.URI] [Uri]. The maximum capacity rule is enforced by the system.
+ * Selecting a new affordance for a slot that is already full will automatically remove the
+ * oldest affordance from the slot.
+ * - Query - to know which affordances are set on which slots, query the [SelectionTable.URI]
+ * [Uri]. The result set will contain rows, each of which with the columns from
+ * [SelectionTable.Columns].
+ * - Delete - to unselect an affordance, removing it from a slot, delete from the
+ * [SelectionTable.URI] [Uri], passing in values for each column.
+ */
+ object SelectionTable {
+ const val TABLE_NAME = "selections"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ object Columns {
+ /** String. Unique ID for the slot. */
+ const val SLOT_ID = "slot_id"
+ /** String. Unique ID for the selected affordance. */
+ const val AFFORDANCE_ID = "affordance_id"
+ /** String. Human-readable name for the affordance. */
+ const val AFFORDANCE_NAME = "affordance_name"
+ }
+ }
+
+ /**
+ * Table for flags.
+ *
+ * Flags are key-value pairs.
+ *
+ * Supported operations:
+ * - Query - to know the values of flags, query the [FlagsTable.URI] [Uri]. The result set will
+ * contain rows, each of which with the columns from [FlagsTable.Columns].
+ */
+ object FlagsTable {
+ const val TABLE_NAME = "flags"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ /**
+ * Flag denoting whether the customizable lock screen quick affordances feature is enabled.
+ */
+ const val FLAG_NAME_FEATURE_ENABLED = "is_feature_enabled"
+
+ object Columns {
+ /** String. Unique ID for the flag. */
+ const val NAME = "name"
+ /** Int. Value of the flag. `1` means `true` and `0` means `false`. */
+ const val VALUE = "value"
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
index 9ea4b57..e226d58 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -38,6 +38,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
import java.util.ArrayList;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 131f728..2f9f5b2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -31,6 +31,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
import java.io.FileDescriptor;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 647dd47..0890465 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -20,7 +20,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
@@ -255,7 +255,8 @@
// Also consider undefined activity type to include tasks in overview right after rebooting
// the device.
final boolean isDockable = taskInfo.supportsMultiWindow
- && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode())
+ && ArrayUtils.contains(
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, taskInfo.getWindowingMode())
&& (taskInfo.getActivityType() == ACTIVITY_TYPE_UNDEFINED
|| ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()));
return new Task(taskKey,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 40c8774..f6c75a2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -61,25 +61,43 @@
* Updates the matrix based on the provided parameters
*/
public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
- int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet,
+ int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx,
+ int taskbarSize, boolean isTablet,
int currentRotation, boolean isRtl) {
boolean isRotated = false;
boolean isOrientationDifferent;
- float fullscreenTaskWidth = screenWidthPx;
- if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) {
- // For landscape, scale the width
- float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
- ? mSplitBounds.leftTaskPercent
- : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
- // Scale landscape width to that of actual screen
- fullscreenTaskWidth = screenWidthPx * taskPercent;
- }
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
RectF thumbnailClipHint = new RectF();
- float canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
- float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+
+ float scaledTaskbarSize;
+ float canvasScreenRatio;
+ if (mSplitBounds != null) {
+ float fullscreenTaskWidth;
+ float fullscreenTaskHeight;
+
+ float taskPercent;
+ if (mSplitBounds.appsStackedVertically) {
+ taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.topTaskPercent
+ : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
+ // Scale portrait height to that of (actual screen - taskbar inset)
+ fullscreenTaskHeight = (screenHeightPx - taskbarSize) * taskPercent;
+ canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+ } else {
+ // For landscape, scale the width
+ taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.leftTaskPercent
+ : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+ // Scale landscape width to that of actual screen
+ fullscreenTaskWidth = screenWidthPx * taskPercent;
+ canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
+ }
+ } else {
+ canvasScreenRatio = (float) canvasWidth / screenWidthPx;
+ }
+ scaledTaskbarSize = taskbarSize * canvasScreenRatio;
thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
float scale = thumbnailData.scale;
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 8a25096..82d70116 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
@@ -53,6 +53,8 @@
InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
public static final int CUJ_RECENTS_SCROLLING =
InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
+ public static final int CUJ_APP_SWIPE_TO_RECENTS =
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -62,7 +64,8 @@
CUJ_QUICK_SWITCH,
CUJ_APP_LAUNCH_FROM_WIDGET,
CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
- CUJ_RECENTS_SCROLLING
+ CUJ_RECENTS_SCROLLING,
+ CUJ_APP_SWIPE_TO_RECENTS
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
new file mode 100644
index 0000000..05372fe
--- /dev/null
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.flags
+
+import android.annotation.BoolRes
+
+object FlagsFactory {
+ private val flagMap = mutableMapOf<String, Flag<*>>()
+
+ val knownFlags: Map<String, Flag<*>>
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
+
+ fun unreleasedFlag(
+ id: Int,
+ name: String,
+ namespace: String = "systemui",
+ teamfood: Boolean = false
+ ): UnreleasedFlag {
+ val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+ FlagsFactory.checkForDupesAndAdd(flag)
+ return flag
+ }
+
+ fun releasedFlag(
+ id: Int,
+ name: String,
+ namespace: String = "systemui",
+ teamfood: Boolean = false
+ ): ReleasedFlag {
+ val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+ FlagsFactory.checkForDupesAndAdd(flag)
+ return flag
+ }
+
+ fun resourceBooleanFlag(
+ id: Int,
+ @BoolRes resourceId: Int,
+ name: String,
+ namespace: String = "systemui",
+ teamfood: Boolean = false
+ ): ResourceBooleanFlag {
+ val flag =
+ ResourceBooleanFlag(
+ id = id,
+ name = name,
+ namespace = namespace,
+ resourceId = resourceId,
+ teamfood = teamfood
+ )
+ FlagsFactory.checkForDupesAndAdd(flag)
+ return flag
+ }
+
+ fun sysPropBooleanFlag(
+ id: Int,
+ name: String,
+ namespace: String = "systemui",
+ default: Boolean = false
+ ): SysPropBooleanFlag {
+ val flag =
+ SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default)
+ FlagsFactory.checkForDupesAndAdd(flag)
+ return flag
+ }
+
+ private fun checkForDupesAndAdd(flag: Flag<*>) {
+ if (flagMap.containsKey(flag.name)) {
+ throw IllegalArgumentException("Name {flag.name} is already registered")
+ }
+ flagMap.forEach {
+ if (it.value.id == flag.id) {
+ throw IllegalArgumentException("Name {flag.id} is already registered")
+ }
+ }
+ flagMap[flag.name] = flag
+ }
+}
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 7b216017..8323d09 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -34,6 +34,9 @@
@Binds
abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
new file mode 100644
index 0000000..27c5699
--- /dev/null
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.flags
+
+import android.annotation.BoolRes
+
+object FlagsFactory {
+ private val flagMap = mutableMapOf<String, Flag<*>>()
+
+ val knownFlags: Map<String, Flag<*>>
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
+
+ fun unreleasedFlag(
+ id: Int,
+ name: String,
+ namespace: String = "systemui",
+ teamfood: Boolean = false
+ ): UnreleasedFlag {
+ // Unreleased flags are always false in this build.
+ val flag = UnreleasedFlag(id = id, name = "", namespace = "", teamfood = false)
+ return flag
+ }
+
+ fun releasedFlag(
+ id: Int,
+ name: String,
+ namespace: String = "systemui",
+ teamfood: Boolean = false
+ ): ReleasedFlag {
+ val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+ flagMap[name] = flag
+ return flag
+ }
+
+ fun resourceBooleanFlag(
+ id: Int,
+ @BoolRes resourceId: Int,
+ name: String,
+ namespace: String = "systemui",
+ teamfood: Boolean = false
+ ): ResourceBooleanFlag {
+ val flag =
+ ResourceBooleanFlag(
+ id = id,
+ name = name,
+ namespace = namespace,
+ resourceId = resourceId,
+ teamfood = teamfood
+ )
+ flagMap[name] = flag
+ return flag
+ }
+
+ fun sysPropBooleanFlag(
+ id: Int,
+ name: String,
+ namespace: String = "systemui",
+ default: Boolean = false
+ ): SysPropBooleanFlag {
+ val flag =
+ SysPropBooleanFlag(id = id, name = name, namespace = namespace, default = default)
+ flagMap[name] = flag
+ return flag
+ }
+}
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 aef8876..87beff7 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -27,4 +27,7 @@
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
}
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 450784e..f59bf8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -69,10 +69,16 @@
super.reloadColor()
}
- override fun setMessage(msg: CharSequence?) {
+ override fun setMessage(msg: CharSequence?, animate: Boolean) {
if ((msg == textAboutToShow && msg != null) || msg == text) {
return
}
+
+ if (!animate) {
+ super.setMessage(msg, animate)
+ return
+ }
+
textAboutToShow = msg
if (animatorSet.isRunning) {
@@ -89,7 +95,7 @@
hideAnimator.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
- super@BouncerKeyguardMessageArea.setMessage(msg)
+ super@BouncerKeyguardMessageArea.setMessage(msg, animate)
}
}
)
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index c9b8712..87e9d56 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -26,6 +26,7 @@
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -43,6 +44,11 @@
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -50,11 +56,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -84,6 +85,7 @@
value.initialize(resources, dozeAmount, 0f)
updateRegionSamplers(value)
+ updateFontSizes()
}
}
@@ -150,7 +152,7 @@
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun = { updateColors() } )
+ updateColors)
}
var smallRegionSampler: RegionSampler? = null
@@ -166,7 +168,7 @@
}
override fun onDensityOrFontScaleChanged() {
- clock?.events?.onFontSettingChanged()
+ updateFontSizes()
}
}
@@ -251,6 +253,13 @@
largeRegionSampler?.stopRegionSampler()
}
+ private fun updateFontSizes() {
+ clock?.smallClock?.events?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat())
+ clock?.largeClock?.events?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
+ }
+
/**
* Dump information for debugging
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 92ba619..3e32cf5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -159,10 +159,12 @@
int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", secondsRemaining);
- mMessageAreaController.setMessage(PluralsMessageFormatter.format(
- mView.getResources(),
- arguments,
- R.string.kg_too_many_failed_attempts_countdown));
+ mMessageAreaController.setMessage(
+ PluralsMessageFormatter.format(
+ mView.getResources(),
+ arguments,
+ R.string.kg_too_many_failed_attempts_countdown),
+ /* animate= */ false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 8ebad6c..40423cd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,6 +5,7 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -22,6 +23,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -46,6 +48,7 @@
*/
private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
+ private ClockController mClock;
private View mStatusArea;
private int mSmartspaceTopOffset;
@@ -95,6 +98,8 @@
}
void setClock(ClockController clock, int statusBarState) {
+ mClock = clock;
+
// Disconnect from existing plugin.
mSmallClockFrame.removeAllViews();
mLargeClockFrame.removeAllViews();
@@ -108,6 +113,35 @@
Log.i(TAG, "Attached new clock views to switch");
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
+ updateClockTargetRegions();
+ }
+
+ void updateClockTargetRegions() {
+ if (mClock != null) {
+ if (mSmallClockFrame.isLaidOut()) {
+ int targetHeight = getResources()
+ .getDimensionPixelSize(R.dimen.small_clock_text_size);
+ mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
+ mSmallClockFrame.getLeft(),
+ mSmallClockFrame.getTop(),
+ mSmallClockFrame.getRight(),
+ mSmallClockFrame.getTop() + targetHeight));
+ }
+
+ if (mLargeClockFrame.isLaidOut()) {
+ int largeClockTopMargin = getResources()
+ .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+ int targetHeight = getResources()
+ .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+ int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
+ + largeClockTopMargin / 2;
+ mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
+ mLargeClockFrame.getLeft(),
+ top,
+ mLargeClockFrame.getRight(),
+ top + targetHeight));
+ }
+ }
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -214,6 +248,10 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
+ if (changed) {
+ post(() -> updateClockTargetRegions());
+ }
+
if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index ace942d..e6aae9b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -77,7 +77,7 @@
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
- private int mKeyguardClockTopMargin = 0;
+ private int mKeyguardSmallClockTopMargin = 0;
private final ClockRegistry.ClockChangeListener mClockChangedListener;
private ViewGroup mStatusArea;
@@ -162,7 +162,7 @@
mClockRegistry.registerClockChangeListener(mClockChangedListener);
setClock(mClockRegistry.createCurrentClock());
mClockEventController.registerListeners(mView);
- mKeyguardClockTopMargin =
+ mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
@@ -244,10 +244,12 @@
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
- mKeyguardClockTopMargin =
+ mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+ mView.updateClockTargetRegions();
}
+
/**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
@@ -327,7 +329,7 @@
return frameHeight / 2 + clockHeight / 2;
} else {
int clockHeight = clock.getSmallClock().getView().getHeight();
- return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index db64f05..2b660de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -21,7 +21,6 @@
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.SystemClock;
-import android.service.trust.TrustAgentService;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.MathUtils;
@@ -68,30 +67,24 @@
private final KeyguardUpdateMonitorCallback mUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onTrustGrantedWithFlags(int flags, int userId) {
- if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
- boolean bouncerVisible = mView.isVisibleToUser();
- boolean temporaryAndRenewable =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)
- != 0;
- boolean initiatedByUser =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
- boolean dismissKeyguard =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
-
- if (initiatedByUser || dismissKeyguard) {
- if ((mViewMediatorCallback.isScreenOn() || temporaryAndRenewable)
- && (bouncerVisible || dismissKeyguard)) {
- if (!bouncerVisible) {
- // The trust agent dismissed the keyguard without the user proving
- // that they are present (by swiping up to show the bouncer). That's
- // fine if the user proved presence via some other way to the trust
- //agent.
- Log.i(TAG, "TrustAgent dismissed Keyguard.");
- }
- mSecurityCallback.dismiss(false /* authenticated */, userId,
- /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid);
- } else {
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ TrustGrantFlags flags, String message) {
+ if (dismissKeyguard) {
+ if (!mView.isVisibleToUser()) {
+ // The trust agent dismissed the keyguard without the user proving
+ // that they are present (by swiping up to show the bouncer). That's
+ // fine if the user proved presence via some other way to the trust
+ // agent.
+ Log.i(TAG, "TrustAgent dismissed Keyguard.");
+ }
+ mSecurityCallback.dismiss(
+ false /* authenticated */,
+ KeyguardUpdateMonitor.getCurrentUser(),
+ /* bypassSecondaryLockScreen */ false,
+ SecurityMode.Invalid
+ );
+ } else {
+ if (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) {
mViewMediatorCallback.playTrustedSound();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 73229c3..faaba63 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -21,6 +21,7 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
@@ -152,7 +153,9 @@
}
public void startAppearAnimation() {
- mMessageAreaController.setMessage(getInitialMessageResId());
+ if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
+ mMessageAreaController.setMessage(getInitialMessageResId());
+ }
mView.startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index a0206f1..8197685 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -50,10 +50,9 @@
override val listening: Boolean,
// keep sorted
val authInterruptActive: Boolean,
- val becauseCannotSkipBouncer: Boolean,
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
- val faceAuthenticated: Boolean,
+ val faceAndFpNotAuthenticated: Boolean,
val faceDisabled: Boolean,
val faceLockedOut: Boolean,
val fpLockedOut: Boolean,
@@ -67,7 +66,9 @@
val secureCameraLaunched: Boolean,
val switchingUser: Boolean,
val udfpsBouncerShowing: Boolean,
-) : KeyguardListenModel()
+ val udfpsFingerDown: Boolean,
+ val userNotTrustedOrDetectionIsNeeded: Boolean,
+ ) : KeyguardListenModel()
/**
* Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index c79fc2c..0e5f8c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -59,6 +59,7 @@
@Nullable
private ViewGroup mContainer;
private int mTopMargin;
+ protected boolean mAnimate;
public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -106,7 +107,7 @@
}
@Override
- public void setMessage(CharSequence msg) {
+ public void setMessage(CharSequence msg, boolean animate) {
if (!TextUtils.isEmpty(msg)) {
securityMessageChanged(msg);
} else {
@@ -115,21 +116,12 @@
}
@Override
- public void setMessage(int resId) {
- CharSequence message = null;
- if (resId != 0) {
- message = getContext().getResources().getText(resId);
- }
- setMessage(message);
- }
-
- @Override
public void formatMessage(int resId, Object... formatArgs) {
CharSequence message = null;
if (resId != 0) {
message = getContext().getString(resId, formatArgs);
}
- setMessage(message);
+ setMessage(message, true);
}
private void securityMessageChanged(CharSequence message) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 2bd3ca5..c29f632 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -92,17 +92,30 @@
}
public void setMessage(CharSequence s) {
- mView.setMessage(s);
+ setMessage(s, true);
+ }
+
+ /**
+ * Sets a message to the underlying text view.
+ */
+ public void setMessage(CharSequence s, boolean animate) {
+ mView.setMessage(s, animate);
}
public void setMessage(int resId) {
- mView.setMessage(resId);
+ String message = resId != 0 ? mView.getResources().getString(resId) : null;
+ setMessage(message);
}
public void setNextMessageColor(ColorStateList colorState) {
mView.setNextMessageColor(colorState);
}
+ /** Returns the message of the underlying TextView. */
+ public CharSequence getMessage() {
+ return mView.getText();
+ }
+
/**
* Reload colors from resources.
**/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 1f0bd54..cdbfb24 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -372,10 +372,13 @@
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", secondsRemaining);
- mMessageAreaController.setMessage(PluralsMessageFormatter.format(
- mView.getResources(),
- arguments,
- R.string.kg_too_many_failed_attempts_countdown));
+ mMessageAreaController.setMessage(
+ PluralsMessageFormatter.format(
+ mView.getResources(),
+ arguments,
+ R.string.kg_too_many_failed_attempts_countdown),
+ /* animate= */ false
+ );
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 2bb3a5f..5c4126e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -727,6 +727,11 @@
mViewMode.reloadColors();
}
+ /** Handles density or font scale changes. */
+ void onDensityOrFontScaleChanged() {
+ mViewMode.onDensityOrFontScaleChanged();
+ }
+
/**
* Enscapsulates the differences between bouncer modes for the container.
*/
@@ -752,6 +757,9 @@
/** Refresh colors */
default void reloadColors() {};
+ /** Handles density or font scale changes. */
+ default void onDensityOrFontScaleChanged() {}
+
/** On a successful auth, optionally handle how the view disappears */
default void startDisappearAnimation(SecurityMode securityMode) {};
@@ -899,14 +907,9 @@
mFalsingA11yDelegate = falsingA11yDelegate;
if (mUserSwitcherViewGroup == null) {
- LayoutInflater.from(v.getContext()).inflate(
- R.layout.keyguard_bouncer_user_switcher,
- mView,
- true);
- mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+ inflateUserSwitcher();
}
updateSecurityViewLocation();
- mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
setupUserSwitcher();
mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback);
}
@@ -937,6 +940,12 @@
}
@Override
+ public void onDensityOrFontScaleChanged() {
+ mView.removeView(mUserSwitcherViewGroup);
+ inflateUserSwitcher();
+ }
+
+ @Override
public void onDestroy() {
mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
}
@@ -1097,11 +1106,19 @@
new KeyguardSecurityViewTransition());
}
int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
+ int viewFlipperBottomMargin = mResources.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_view_mode_view_flipper_bottom_margin);
+ int userSwitcherBottomMargin = mResources.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_view_mode_user_switcher_bottom_margin);
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans);
- constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
- constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, mViewFlipper.getId(),
+ TOP, userSwitcherBottomMargin);
+ constraintSet.connect(mViewFlipper.getId(), TOP, mUserSwitcherViewGroup.getId(),
+ BOTTOM);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM,
+ viewFlipperBottomMargin);
constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID);
constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID);
constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
@@ -1137,6 +1154,15 @@
}
}
+ private void inflateUserSwitcher() {
+ LayoutInflater.from(mView.getContext()).inflate(
+ R.layout.keyguard_bouncer_user_switcher,
+ mView,
+ true);
+ mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+ mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
+ }
+
interface UserSwitcherCallback {
void showUnlockToContinueMessage();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 7a49926..01be33e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -251,6 +251,11 @@
public void onUiModeChanged() {
reloadColors();
}
+
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+ }
};
private boolean mBouncerVisible = false;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -727,6 +732,14 @@
mView.reloadColors();
}
+ /** Handles density or font scale changes. */
+ private void onDensityOrFontScaleChanged() {
+ mSecurityViewFlipperController.onDensityOrFontScaleChanged();
+ mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
+ mKeyguardSecurityCallback);
+ mView.onDensityOrFontScaleChanged();
+ }
+
static class Factory {
private final KeyguardSecurityContainer mView;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index bddf4b0..25afe11 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -83,6 +83,13 @@
}
}
+ /** Handles density or font scale changes. */
+ public void onDensityOrFontScaleChanged() {
+ mView.removeAllViews();
+ mChildren.clear();
+ }
+
+
@VisibleForTesting
KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 83e23bd..8b9823b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import android.content.Context;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -112,4 +113,11 @@
mKeyguardSlice.dump(pw, args);
}
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("KeyguardStatusView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f08b1be..ce22a81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -144,6 +144,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
@@ -263,6 +264,7 @@
"com.android.settings", "com.android.settings.FallbackHome");
private final Context mContext;
+ private final UserTracker mUserTracker;
private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsPrimaryUser;
private final AuthController mAuthController;
@@ -298,8 +300,8 @@
private boolean mCredentialAttempted;
private boolean mKeyguardGoingAway;
private boolean mGoingToSleep;
- private boolean mBouncerFullyShown;
- private boolean mBouncerIsOrWillBeShowing;
+ private boolean mPrimaryBouncerFullyShown;
+ private boolean mPrimaryBouncerIsOrWillBeShowing;
private boolean mUdfpsBouncerShowing;
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
@@ -470,21 +472,12 @@
FACE_AUTH_TRIGGERED_TRUST_DISABLED);
}
- mLogger.logTrustChanged(wasTrusted, enabled, userId);
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onTrustChanged(userId);
- if (enabled && flags != 0) {
- cb.onTrustGrantedWithFlags(flags, userId);
- }
- }
- }
-
- if (KeyguardUpdateMonitor.getCurrentUser() == userId) {
- CharSequence message = null;
- final boolean userHasTrust = getUserHasTrust(userId);
- if (userHasTrust && trustGrantedMessages != null) {
+ if (enabled) {
+ String message = null;
+ if (KeyguardUpdateMonitor.getCurrentUser() == userId
+ && trustGrantedMessages != null) {
+ // Show the first non-empty string provided by a trust agent OR intentionally pass
+ // an empty string through (to prevent the default trust agent string from showing)
for (String msg : trustGrantedMessages) {
message = msg;
if (!TextUtils.isEmpty(message)) {
@@ -493,17 +486,38 @@
}
}
- if (message != null) {
- mLogger.logShowTrustGrantedMessage(message.toString());
- }
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.showTrustGrantedMessage(message);
+ mLogger.logTrustGrantedWithFlags(flags, userId, message);
+ if (userId == getCurrentUser()) {
+ final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustGrantedForCurrentUser(
+ shouldDismissKeyguardOnTrustGrantedWithCurrentUser(trustGrantFlags),
+ trustGrantFlags, message);
+ }
}
}
}
+ mLogger.logTrustChanged(wasTrusted, enabled, userId);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustChanged(userId);
+ }
+ }
+ }
+
+ /**
+ * Whether the trust granted call with its passed flags should dismiss keyguard.
+ * It's assumed that the trust was granted for the current user.
+ */
+ private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
+ final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing;
+ return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
+ && (mDeviceInteractive || flags.temporaryAndRenewable())
+ && (isBouncerShowing || flags.dismissKeyguardRequested());
}
@Override
@@ -780,9 +794,9 @@
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
+ mLogger.logFingerprintSuccess(userId, isStrongBiometric);
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_FP_AUTHENTICATED);
- mLogger.logFingerprintSuccess(userId, isStrongBiometric);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -856,13 +870,7 @@
mHandler.removeCallbacks(mFpCancelNotReceived);
}
try {
- final int userId;
- try {
- userId = ActivityManager.getService().getCurrentUser().id;
- } catch (RemoteException e) {
- mLogger.logException(e, "Failed to get current user id");
- return;
- }
+ final int userId = mUserTracker.getUserId();
if (userId != authUserId) {
mLogger.logFingerprintAuthForWrongUser(authUserId);
return;
@@ -1080,13 +1088,7 @@
mLogger.d("Aborted successful auth because device is going to sleep.");
return;
}
- final int userId;
- try {
- userId = ActivityManager.getService().getCurrentUser().id;
- } catch (RemoteException e) {
- mLogger.logException(e, "Failed to get current user id");
- return;
- }
+ final int userId = mUserTracker.getUserId();
if (userId != authUserId) {
mLogger.logFaceAuthForWrongUser(authUserId);
return;
@@ -1648,8 +1650,9 @@
public void onAuthenticationFailed() {
String reason =
mKeyguardBypassController.canBypass() ? "bypass"
- : mUdfpsBouncerShowing ? "udfpsBouncer" :
- mBouncerFullyShown ? "bouncer" : "udfpsFpDown";
+ : mUdfpsBouncerShowing ? "udfpsBouncer"
+ : mPrimaryBouncerFullyShown ? "bouncer"
+ : "udfpsFpDown";
requestActiveUnlock(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
"faceFailure-" + reason);
@@ -1935,6 +1938,7 @@
@Inject
protected KeyguardUpdateMonitor(
Context context,
+ UserTracker userTracker,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings,
@@ -1967,6 +1971,7 @@
FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
mContext = context;
mSubscriptionManager = subscriptionManager;
+ mUserTracker = userTracker;
mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
@@ -2041,7 +2046,7 @@
handleKeyguardReset();
break;
case MSG_KEYGUARD_BOUNCER_CHANGED:
- handleKeyguardBouncerChanged(msg.arg1, msg.arg2);
+ handlePrimaryBouncerChanged(msg.arg1, msg.arg2);
break;
case MSG_REPORT_EMERGENCY_CALL_ACTION:
handleReportEmergencyCallAction();
@@ -2199,7 +2204,7 @@
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mIsPrimaryUser = mUserManager.isPrimaryUser();
- int user = ActivityManager.getCurrentUser();
+ int user = mUserTracker.getUserId();
mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
@@ -2511,7 +2516,7 @@
requestOrigin,
extraReason, canFaceBypass
|| mUdfpsBouncerShowing
- || mBouncerFullyShown
+ || mPrimaryBouncerFullyShown
|| mAuthController.isUdfpsFingerDown());
}
@@ -2532,7 +2537,7 @@
private boolean shouldTriggerActiveUnlock() {
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
- final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing
+ final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mUdfpsBouncerShowing
|| (isKeyguardVisible() && !mGoingToSleep
&& mStatusBarState != StatusBarState.SHADE_LOCKED);
@@ -2611,7 +2616,7 @@
final boolean shouldListenKeyguardState =
isKeyguardVisible()
|| !mDeviceInteractive
- || (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
+ || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
|| mGoingToSleep
|| shouldListenForFingerprintAssistant
|| (mKeyguardOccluded && mIsDreaming)
@@ -2630,8 +2635,8 @@
&& mIsPrimaryUser
&& biometricEnabledForUser;
- final boolean shouldListenBouncerState =
- !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
+ final boolean shouldListenBouncerState = !(mFingerprintLockedOut
+ && mPrimaryBouncerIsOrWillBeShowing && mCredentialAttempted);
final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
@@ -2656,7 +2661,7 @@
user,
shouldListen,
biometricEnabledForUser,
- mBouncerIsOrWillBeShowing,
+ mPrimaryBouncerIsOrWillBeShowing,
userCanSkipBouncer,
mCredentialAttempted,
mDeviceInteractive,
@@ -2706,14 +2711,14 @@
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
- // bouncer, unless we're bypassing and need to auto-dismiss the lock screen even when
- // TrustAgents or biometrics are keeping the device unlocked.
- final boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass;
+ // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss
+ // the lock screen even when TrustAgents are keeping the device unlocked.
+ final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
// Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
// Lock-down mode shouldn't scan, since it is more explicit.
boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
- && !mBouncerFullyShown);
+ && !mPrimaryBouncerFullyShown);
// If the device supports face detection (without authentication) and bypass is enabled,
// allow face scanning to happen if the device is in lockdown mode.
@@ -2725,27 +2730,28 @@
strongAuthAllowsScanning = false;
}
- // If the face has recently been authenticated do not attempt to authenticate again.
- final boolean faceAuthenticated = getIsFaceAuthenticated();
+ // If the face or fp has recently been authenticated do not attempt to authenticate again.
+ final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
final boolean faceDisabledForUser = isFaceDisabled(user);
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
+ final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
- (mBouncerFullyShown
+ (mPrimaryBouncerFullyShown
|| mAuthInterruptActive
|| mOccludingAppRequestingFace
|| awakeKeyguard
|| shouldListenForFaceAssistant
- || mAuthController.isUdfpsFingerDown()
+ || isUdfpsFingerDown
|| mUdfpsBouncerShowing)
- && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
+ && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
- && !faceAuthenticated
+ && faceAndFpNotAuthenticated
&& !mGoingToSleep
// We only care about fp locked out state and not face because we still trigger
// face auth even when face is locked out to show the user a message that face
@@ -2759,10 +2765,9 @@
user,
shouldListen,
mAuthInterruptActive,
- becauseCannotSkipBouncer,
biometricEnabledForUser,
- mBouncerFullyShown,
- faceAuthenticated,
+ mPrimaryBouncerFullyShown,
+ faceAndFpNotAuthenticated,
faceDisabledForUser,
isFaceLockedOut(),
fpLockedOut,
@@ -2775,7 +2780,9 @@
strongAuthAllowsScanning,
mSecureCameraLaunched,
mSwitchingUser,
- mUdfpsBouncerShowing));
+ mUdfpsBouncerShowing,
+ isUdfpsFingerDown,
+ userNotTrustedOrDetectionIsNeeded));
return shouldListen;
}
@@ -3291,17 +3298,19 @@
/**
* Handle {@link #MSG_KEYGUARD_BOUNCER_CHANGED}
*
- * @see #sendKeyguardBouncerChanged(boolean, boolean)
+ * @see #sendPrimaryBouncerChanged(boolean, boolean)
*/
- private void handleKeyguardBouncerChanged(int bouncerIsOrWillBeShowing, int bouncerFullyShown) {
+ private void handlePrimaryBouncerChanged(int primaryBouncerIsOrWillBeShowing,
+ int primaryBouncerFullyShown) {
Assert.isMainThread();
- final boolean wasBouncerIsOrWillBeShowing = mBouncerIsOrWillBeShowing;
- final boolean wasBouncerFullyShown = mBouncerFullyShown;
- mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
- mBouncerFullyShown = bouncerFullyShown == 1;
- mLogger.logKeyguardBouncerChanged(mBouncerIsOrWillBeShowing, mBouncerFullyShown);
+ final boolean wasPrimaryBouncerIsOrWillBeShowing = mPrimaryBouncerIsOrWillBeShowing;
+ final boolean wasPrimaryBouncerFullyShown = mPrimaryBouncerFullyShown;
+ mPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing == 1;
+ mPrimaryBouncerFullyShown = primaryBouncerFullyShown == 1;
+ mLogger.logPrimaryKeyguardBouncerChanged(mPrimaryBouncerIsOrWillBeShowing,
+ mPrimaryBouncerFullyShown);
- if (mBouncerFullyShown) {
+ if (mPrimaryBouncerFullyShown) {
// If the bouncer is shown, always clear this flag. This can happen in the following
// situations: 1) Default camera with SHOW_WHEN_LOCKED is not chosen yet. 2) Secure
// camera requests dismiss keyguard (tapping on photos for example). When these happen,
@@ -3311,18 +3320,18 @@
mCredentialAttempted = false;
}
- if (wasBouncerIsOrWillBeShowing != mBouncerIsOrWillBeShowing) {
+ if (wasPrimaryBouncerIsOrWillBeShowing != mPrimaryBouncerIsOrWillBeShowing) {
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
+ cb.onKeyguardBouncerStateChanged(mPrimaryBouncerIsOrWillBeShowing);
}
}
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
- if (wasBouncerFullyShown != mBouncerFullyShown) {
- if (mBouncerFullyShown) {
+ if (wasPrimaryBouncerFullyShown != mPrimaryBouncerFullyShown) {
+ if (mPrimaryBouncerFullyShown) {
requestActiveUnlock(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
"bouncerFullyShown");
@@ -3330,7 +3339,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onKeyguardBouncerFullyShowingChanged(mBouncerFullyShown);
+ cb.onKeyguardBouncerFullyShowingChanged(mPrimaryBouncerFullyShown);
}
}
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
@@ -3481,14 +3490,15 @@
}
/**
- * @see #handleKeyguardBouncerChanged(int, int)
+ * @see #handlePrimaryBouncerChanged(int, int)
*/
- public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
- boolean bouncerFullyShown) {
- mLogger.logSendKeyguardBouncerChanged(bouncerIsOrWillBeShowing, bouncerFullyShown);
+ public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing,
+ boolean primaryBouncerFullyShown) {
+ mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+ primaryBouncerFullyShown);
Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
- message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
- message.arg2 = bouncerFullyShown ? 1 : 0;
+ message.arg1 = primaryBouncerIsOrWillBeShowing ? 1 : 0;
+ message.arg2 = primaryBouncerFullyShown ? 1 : 0;
message.sendToTarget();
}
@@ -3822,7 +3832,7 @@
pw.println(" " + subId + "=" + mServiceStates.get(subId));
}
if (mFpm != null && mFpm.isHardwareDetected()) {
- final int userId = ActivityManager.getCurrentUser();
+ final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
@@ -3848,7 +3858,8 @@
if (isUdfpsSupported()) {
pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true));
- pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
+ pw.println(" mPrimaryBouncerIsOrWillBeShowing="
+ + mPrimaryBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
} else if (isSfpsSupported()) {
@@ -3861,7 +3872,7 @@
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
- final int userId = ActivityManager.getCurrentUser();
+ final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
pw.println(" Face authentication state (user=" + userId + ")");
@@ -3880,7 +3891,7 @@
pw.println(" mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched);
- pw.println(" mBouncerFullyShown=" + mBouncerFullyShown);
+ pw.println(" mPrimaryBouncerFullyShown=" + mPrimaryBouncerFullyShown);
pw.println(" mNeedsSlowUnlockTransition=" + mNeedsSlowUnlockTransition);
}
mListenModels.print(pw);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index c06e1dc..1d58fc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -19,6 +19,7 @@
import android.telephony.TelephonyManager;
import android.view.WindowManagerPolicyConstants;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -174,14 +175,14 @@
public void onTrustManagedChanged(int userId) { }
/**
- * Called after trust was granted with non-zero flags.
+ * Called after trust was granted.
+ * @param dismissKeyguard whether the keyguard should be dismissed as a result of the
+ * trustGranted
+ * @param message optional message the trust agent has provided to show that should indicate
+ * why trust was granted.
*/
- public void onTrustGrantedWithFlags(int flags, int userId) { }
-
- /**
- * Called when setting the trust granted message.
- */
- public void showTrustGrantedMessage(@Nullable CharSequence message) { }
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ @NonNull TrustGrantFlags flags, @Nullable String message) { }
/**
* Called when a biometric has been acquired.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 90f0446..6c3c246 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -50,16 +50,11 @@
/**
* Resets the state of Keyguard View.
- * @param hideBouncerWhenShowing
+ * @param hideBouncerWhenShowing when true, hides the primary and alternate bouncers if showing.
*/
void reset(boolean hideBouncerWhenShowing);
/**
- * Stop showing any alternate auth methods.
- */
- void resetAlternateAuth(boolean forceUpdateScrim);
-
- /**
* Called when the device started going to sleep.
*/
default void onStartedGoingToSleep() {};
@@ -156,20 +151,24 @@
void notifyKeyguardAuthenticated(boolean strongAuth);
/**
- * Shows the Bouncer.
- *
+ * Shows the primary bouncer.
*/
- void showBouncer(boolean scrimmed);
+ void showPrimaryBouncer(boolean scrimmed);
/**
- * Returns {@code true} when the bouncer is currently showing
+ * When the primary bouncer is fully visible or is showing but animation didn't finish yet.
+ */
+ boolean primaryBouncerIsOrWillBeShowing();
+
+ /**
+ * Returns {@code true} when the primary bouncer or alternate bouncer is currently showing
*/
boolean isBouncerShowing();
/**
- * When bouncer is fully visible or it is showing but animation didn't finish yet.
+ * Stop showing the alternate bouncer, if showing.
*/
- boolean bouncerIsOrWillBeShowing();
+ void hideAlternateBouncer(boolean forceUpdateScrim);
// TODO: Deprecate registerStatusBar in KeyguardViewController interface. It is currently
// only used for testing purposes in StatusBarKeyguardViewManager, and it prevents us from
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 0a82968..34a5ef7 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -158,6 +158,10 @@
return mLockIconCenter.y - mRadius;
}
+ float getLocationBottom() {
+ return mLockIconCenter.y + mRadius;
+ }
+
/**
* Updates the icon its default state where no visual is shown.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8fbbd38..cf246b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -282,6 +282,10 @@
return mView.getLocationTop();
}
+ public float getBottom() {
+ return mView.getLocationBottom();
+ }
+
private void updateVisibility() {
if (mCancelDelayedUpdateVisibilityRunnable != null) {
mCancelDelayedUpdateVisibilityRunnable.run();
@@ -697,7 +701,7 @@
"lock-screen-lock-icon-longpress",
TOUCH_VIBRATION_ATTRIBUTES);
- mKeyguardViewController.showBouncer(/* scrim */ true);
+ mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
index 777bd19..3392a1c 100644
--- a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
+++ b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
@@ -23,9 +23,10 @@
/** Set text color for the next security message. */
default void setNextMessageColor(ColorStateList colorState) {}
- void setMessage(CharSequence msg);
-
- void setMessage(int resId);
+ /**
+ * Sets a message to the underlying text view.
+ */
+ void setMessage(CharSequence msg, boolean animate);
void formatMessage(int resId, Object... formatArgs);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
new file mode 100644
index 0000000..d33732c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
@@ -0,0 +1,98 @@
+/*
+ * 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.keyguard;
+
+import android.service.trust.TrustAgentService;
+
+import java.util.Objects;
+
+/**
+ * Translating {@link android.service.trust.TrustAgentService.GrantTrustFlags} to a more
+ * parsable object. These flags are requested by a TrustAgent.
+ */
+public class TrustGrantFlags {
+ final int mFlags;
+
+ public TrustGrantFlags(int flags) {
+ this.mFlags = flags;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER} */
+ public boolean isInitiatedByUser() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
+ }
+
+ /**
+ * Trust agent is requesting to dismiss the keyguard.
+ * See {@link TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD}.
+ *
+ * This does not guarantee that the keyguard is dismissed.
+ * KeyguardUpdateMonitor makes the final determination whether the keyguard should be dismissed.
+ * {@link KeyguardUpdateMonitorCallback#onTrustGrantedForCurrentUser(
+ * boolean, TrustGrantFlags, String).
+ */
+ public boolean dismissKeyguardRequested() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE} */
+ public boolean temporaryAndRenewable() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE} */
+ public boolean displayMessage() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TrustGrantFlags)) {
+ return false;
+ }
+
+ return ((TrustGrantFlags) o).mFlags == this.mFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFlags);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(mFlags);
+ sb.append("]=");
+
+ if (isInitiatedByUser()) {
+ sb.append("initiatedByUser|");
+ }
+ if (dismissKeyguardRequested()) {
+ sb.append("dismissKeyguard|");
+ }
+ if (temporaryAndRenewable()) {
+ sb.append("temporaryAndRenewable|");
+ }
+ if (displayMessage()) {
+ sb.append("displayMessage|");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 9a0bfc1..122c521 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -29,24 +29,25 @@
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.Observer;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManager.DockEventListener;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.settings.CurrentUserObservable;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -69,7 +70,8 @@
private final ContentResolver mContentResolver;
private final SettingsWrapper mSettingsWrapper;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
- private final CurrentUserObservable mCurrentUserObservable;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
/**
* Observe settings changes to know when to switch the clock face.
@@ -80,7 +82,7 @@
public void onChange(boolean selfChange, Collection<Uri> uris,
int flags, int userId) {
if (Objects.equals(userId,
- mCurrentUserObservable.getCurrentUser().getValue())) {
+ mUserTracker.getUserId())) {
reload();
}
}
@@ -89,7 +91,13 @@
/**
* Observe user changes and react by potentially loading the custom clock for the new user.
*/
- private final Observer<Integer> mCurrentUserObserver = (newUserId) -> reload();
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reload();
+ }
+ };
private final PluginManager mPluginManager;
@Nullable private final DockManager mDockManager;
@@ -129,22 +137,24 @@
@Inject
public ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
- @Nullable DockManager dockManager, BroadcastDispatcher broadcastDispatcher) {
+ @Nullable DockManager dockManager, UserTracker userTracker,
+ @Main Executor mainExecutor) {
this(context, layoutInflater, pluginManager, colorExtractor,
- context.getContentResolver(), new CurrentUserObservable(broadcastDispatcher),
+ context.getContentResolver(), userTracker, mainExecutor,
new SettingsWrapper(context.getContentResolver()), dockManager);
}
@VisibleForTesting
ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
- ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
+ ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
SettingsWrapper settingsWrapper, DockManager dockManager) {
mContext = context;
mPluginManager = pluginManager;
mContentResolver = contentResolver;
mSettingsWrapper = settingsWrapper;
- mCurrentUserObservable = currentUserObservable;
+ mUserTracker = userTracker;
+ mMainExecutor = mainExecutor;
mDockManager = dockManager;
mPreviewClocks = new AvailableClocks();
@@ -226,7 +236,7 @@
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
false, mContentObserver, UserHandle.USER_ALL);
- mCurrentUserObservable.getCurrentUser().observeForever(mCurrentUserObserver);
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
if (mDockManager != null) {
mDockManager.addListener(mDockEventListener);
}
@@ -235,7 +245,7 @@
private void unregister() {
mPluginManager.removePluginListener(mPreviewClocks);
mContentResolver.unregisterContentObserver(mContentObserver);
- mCurrentUserObservable.getCurrentUser().removeObserver(mCurrentUserObserver);
+ mUserTracker.removeCallback(mUserChangedCallback);
if (mDockManager != null) {
mDockManager.removeListener(mDockEventListener);
}
@@ -363,7 +373,7 @@
ClockPlugin plugin = null;
if (ClockManager.this.isDocked()) {
final String name = mSettingsWrapper.getDockedClockFace(
- mCurrentUserObservable.getCurrentUser().getValue());
+ mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
if (plugin != null) {
@@ -372,7 +382,7 @@
}
}
final String name = mSettingsWrapper.getLockScreenCustomClockFace(
- mCurrentUserObservable.getCurrentUser().getValue());
+ mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 9767313..676979c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -17,17 +17,20 @@
package com.android.keyguard.dagger;
import android.content.Context;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.UserHandle;
+import android.view.LayoutInflater;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
-import com.android.systemui.shared.plugins.PluginManager;
import dagger.Module;
import dagger.Provides;
@@ -42,14 +45,16 @@
@Application Context context,
PluginManager pluginManager,
@Main Handler handler,
- DefaultClockProvider defaultClockProvider,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ @Main Resources resources,
+ LayoutInflater layoutInflater) {
return new ClockRegistry(
context,
pluginManager,
handler,
featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
UserHandle.USER_ALL,
- defaultClockProvider);
+ new DefaultClockProvider(context, layoutInflater, resources),
+ context.getString(R.string.lockscreen_clock_id_fallback));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index 8fc8600..a7d4455 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -21,10 +21,7 @@
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -50,10 +47,4 @@
static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
return view.findViewById(R.id.user_switcher_container);
}
-
- /** */
- @Binds
- @KeyguardStatusBarViewScope
- abstract StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 32ce537..9e58500 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -18,14 +18,11 @@
import com.android.systemui.log.dagger.KeyguardLog
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.ERROR
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.plugins.log.LogLevel.VERBOSE
import com.android.systemui.plugins.log.LogLevel.WARNING
-import com.android.systemui.plugins.log.MessageInitializer
-import com.android.systemui.plugins.log.MessagePrinter
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -37,18 +34,16 @@
* an overkill.
*/
class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
- fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
- fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+ fun e(@CompileTimeConstant msg: String) = buffer.log(TAG, ERROR, msg)
- fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+ fun v(@CompileTimeConstant msg: String) = buffer.log(TAG, VERBOSE, msg)
- fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+ fun w(@CompileTimeConstant msg: String) = buffer.log(TAG, WARNING, msg)
- fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
-
- private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
- buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+ fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+ buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
}
fun v(msg: String, arg: Any) {
@@ -61,17 +56,24 @@
// TODO: remove after b/237743330 is fixed
fun logStatusBarCalculatedAlpha(alpha: Float) {
- debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+ buffer.log(TAG, DEBUG, { double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
}
// TODO: remove after b/237743330 is fixed
fun logStatusBarExplicitAlpha(alpha: Float) {
- debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+ buffer.log(
+ TAG,
+ DEBUG,
+ { double1 = alpha.toDouble() },
+ { "new mExplicitAlpha value: $double1" }
+ )
}
// TODO: remove after b/237743330 is fixed
fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
- debugLog(
+ buffer.log(
+ TAG,
+ DEBUG,
{
int1 = visibility
double1 = alpha.toDouble()
@@ -80,4 +82,22 @@
{ "changing visibility to $int1 with alpha $double1 in state: $str1" }
)
}
+
+ @JvmOverloads
+ fun logBiometricMessage(
+ @CompileTimeConstant context: String,
+ msgId: Int? = null,
+ msg: String? = null
+ ) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = context
+ str2 = "$msgId"
+ str3 = msg
+ },
+ { "$str1 msgId: $str2 msg: $str3" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 6276142d..6763700 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -25,6 +25,7 @@
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TrustGrantFlags
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -166,13 +167,16 @@
{ "Previously active sub id $int1 is now invalid, will remove" })
}
- fun logKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean, bouncerFullyShown: Boolean) {
+ fun logPrimaryKeyguardBouncerChanged(
+ primaryBouncerIsOrWillBeShowing: Boolean,
+ primaryBouncerFullyShown: Boolean
+ ) {
logBuffer.log(TAG, DEBUG, {
- bool1 = bouncerIsOrWillBeShowing
- bool2 = bouncerFullyShown
+ bool1 = primaryBouncerIsOrWillBeShowing
+ bool2 = primaryBouncerFullyShown
}, {
- "handleKeyguardBouncerChanged " +
- "bouncerIsOrWillBeShowing=$bool1 bouncerFullyShowing=$bool2"
+ "handlePrimaryBouncerChanged " +
+ "primaryBouncerIsOrWillBeShowing=$bool1 primaryBouncerFullyShown=$bool2"
})
}
@@ -229,16 +233,16 @@
{ "Retrying fingerprint attempt: $int1" })
}
- fun logSendKeyguardBouncerChanged(
- bouncerIsOrWillBeShowing: Boolean,
- bouncerFullyShown: Boolean,
+ fun logSendPrimaryBouncerChanged(
+ primaryBouncerIsOrWillBeShowing: Boolean,
+ primaryBouncerFullyShown: Boolean,
) {
logBuffer.log(TAG, DEBUG, {
- bool1 = bouncerIsOrWillBeShowing
- bool2 = bouncerFullyShown
+ bool1 = primaryBouncerIsOrWillBeShowing
+ bool2 = primaryBouncerFullyShown
}, {
- "sendKeyguardBouncerChanged bouncerIsOrWillBeShowing=$bool1 " +
- "bouncerFullyShown=$bool2"
+ "sendPrimaryBouncerChanged primaryBouncerIsOrWillBeShowing=$bool1 " +
+ "primaryBouncerFullyShown=$bool2"
})
}
@@ -365,12 +369,16 @@
}, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
}
- fun logShowTrustGrantedMessage(
+ fun logTrustGrantedWithFlags(
+ flags: Int,
+ userId: Int,
message: String?
) {
logBuffer.log(TAG, DEBUG, {
+ int1 = flags
+ int2 = userId
str1 = message
- }, { "showTrustGrantedMessage message$str1" })
+ }, { "trustGrantedWithFlags[user=$int2] flags=${TrustGrantFlags(int1)} message=$str1" })
}
fun logTrustChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 3015710..eee705d 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -26,8 +26,6 @@
import kotlin.math.roundToInt
-const val TAG = "CameraAvailabilityListener"
-
/**
* Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
* protection around a display cutout based on config_frontBuiltInDisplayCutoutProtection and
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index a89cbf5..9ac45b3 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -4,30 +4,32 @@
import android.content.Context
import android.content.pm.PackageManager
import android.util.Log
+import com.android.internal.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.FlagListenable
import com.android.systemui.flags.Flags
-import javax.inject.Inject
+import com.android.systemui.settings.UserTracker
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
+import javax.inject.Inject
@SysUISingleton
class ChooserSelector @Inject constructor(
private val context: Context,
+ private val userTracker: UserTracker,
private val featureFlags: FeatureFlags,
@Application private val coroutineScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher
+ @Background private val bgDispatcher: CoroutineDispatcher,
) : CoreStartable {
- private val packageManager = context.packageManager
private val chooserComponent = ComponentName.unflattenFromString(
- context.resources.getString(ChooserSelectorResourceHelper.CONFIG_CHOOSER_ACTIVITY))
+ context.resources.getString(R.string.config_chooserActivity))
override fun start() {
coroutineScope.launch {
@@ -56,10 +58,17 @@
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
- try {
- packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
- } catch (e: IllegalArgumentException) {
- Log.w("ChooserSelector", "Unable to set IntentResolver enabled=" + enabled, e)
+ userTracker.userProfiles.forEach {
+ try {
+ context.createContextAsUser(it.userHandle, /* flags = */ 0).packageManager
+ .setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+ } catch (e: IllegalArgumentException) {
+ Log.w(
+ "ChooserSelector",
+ "Unable to set IntentResolver enabled=$enabled for user ${it.id}",
+ e,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a5fdc68..ef16a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -61,6 +61,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
@@ -70,8 +71,8 @@
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -357,6 +358,7 @@
@Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
@Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
@Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
+ @Inject Lazy<UserTracker> mUserTrackerLazy;
@Inject
public Dependency() {
@@ -564,6 +566,7 @@
mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
+ mProviders.put(UserTracker.class, mUserTrackerLazy::get);
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index 5d52056..90ecb46 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -169,7 +169,7 @@
return
}
cutoutPath.reset()
- display.getDisplayInfo(displayInfo)
+ context.display?.getDisplayInfo(displayInfo)
displayInfo.displayCutout?.cutoutPath?.let { path -> cutoutPath.set(path) }
invalidate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index 95f666c..1bb0329 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -21,8 +21,8 @@
import android.view.View;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.ViewProvider;
-import com.android.systemui.shared.plugins.PluginManager;
/**
* Define an interface or abstract class as follows that includes the
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 9aa5fae..70750a1 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -72,7 +72,8 @@
Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
Key.HAS_CLICKED_NUDGE_TO_SETUP_DREAM,
- Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM
+ Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM,
+ Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED
})
// TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
public @interface Key {
@@ -117,6 +118,7 @@
String ACCESSIBILITY_FLOATING_MENU_POSITION = "AccessibilityFloatingMenuPosition";
String HAS_CLICKED_NUDGE_TO_SETUP_DREAM = "HasClickedNudgeToSetupDream";
String HAS_DISMISSED_NUDGE_TO_SETUP_DREAM = "HasDismissedNudgeToSetupDream";
+ String HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED = "HasAccessibilityFloatingMenuTucked";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 11d579d..7e3b1389 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -26,7 +26,6 @@
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -170,6 +169,7 @@
private Display.Mode mDisplayMode;
@VisibleForTesting
protected DisplayInfo mDisplayInfo = new DisplayInfo();
+ private DisplayCutout mDisplayCutout;
@VisibleForTesting
protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
@@ -384,6 +384,7 @@
mRotation = mDisplayInfo.rotation;
mDisplayMode = mDisplayInfo.getMode();
mDisplayUniqueId = mDisplayInfo.uniqueId;
+ mDisplayCutout = mDisplayInfo.displayCutout;
mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
mDisplayUniqueId);
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
@@ -899,7 +900,7 @@
private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- int newUserId = ActivityManager.getCurrentUser();
+ int newUserId = mUserTracker.getUserId();
if (DEBUG) {
Log.d(TAG, "UserSwitched newUserId=" + newUserId);
}
@@ -1022,7 +1023,8 @@
mRoundedCornerResDelegate.dump(pw, args);
}
- private void updateConfiguration() {
+ @VisibleForTesting
+ void updateConfiguration() {
Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
"must call on " + mHandler.getLooper().getThread()
+ ", but was " + Thread.currentThread());
@@ -1033,11 +1035,14 @@
mDotViewController.setNewRotation(newRotation);
}
final Display.Mode newMod = mDisplayInfo.getMode();
+ final DisplayCutout newCutout = mDisplayInfo.displayCutout;
if (!mPendingConfigChange
- && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
+ && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+ || !Objects.equals(newCutout, mDisplayCutout))) {
mRotation = newRotation;
mDisplayMode = newMod;
+ mDisplayCutout = newCutout;
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
getPhysicalPixelDisplaySizeRatio());
if (mScreenDecorHwcLayer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 0e7deeb..ffdd861 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -113,7 +113,8 @@
setTheme(R.style.Theme_SystemUI);
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
- IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ IntentFilter bootCompletedFilter = new
+ IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
// If SF GPU context priority is set to realtime, then SysUI should run at high.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 0a2dc5b..d60cc75 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -291,8 +291,11 @@
mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
- mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
- mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+ if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+ // These two actions require the CentralSurfaces instance.
+ mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
+ mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+ }
mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a6e767c..ec15d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -158,6 +158,10 @@
private View mTopDrag;
private View mRightDrag;
private View mBottomDrag;
+ private ImageView mTopLeftCornerView;
+ private ImageView mTopRightCornerView;
+ private ImageView mBottomLeftCornerView;
+ private ImageView mBottomRightCornerView;
private final Configuration mConfiguration;
@NonNull
@@ -357,13 +361,15 @@
return false;
}
- private void changeMagnificationSize(@MagnificationSize int index) {
+ @VisibleForTesting
+ void changeMagnificationSize(@MagnificationSize int index) {
final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
setWindowSize(size, size);
}
- private void setEditMagnifierSizeMode(boolean enable) {
+ @VisibleForTesting
+ void setEditMagnifierSizeMode(boolean enable) {
mEditSizeEnable = enable;
applyResourcesValues();
@@ -639,10 +645,37 @@
Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
mMirrorView.getWidth() - mBorderDragSize,
mMirrorView.getHeight() - mBorderDragSize);
+
+ Region tapExcludeRegion = new Region();
+
Rect dragArea = new Rect();
mDragView.getHitRect(dragArea);
- regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE);
+ Rect topLeftArea = new Rect();
+ mTopLeftCornerView.getHitRect(topLeftArea);
+
+ Rect topRightArea = new Rect();
+ mTopRightCornerView.getHitRect(topRightArea);
+
+ Rect bottomLeftArea = new Rect();
+ mBottomLeftCornerView.getHitRect(bottomLeftArea);
+
+ Rect bottomRightArea = new Rect();
+ mBottomRightCornerView.getHitRect(bottomRightArea);
+
+ Rect closeArea = new Rect();
+ mCloseView.getHitRect(closeArea);
+
+ // add tapExcludeRegion for Drag or close
+ tapExcludeRegion.op(dragArea, Region.Op.UNION);
+ tapExcludeRegion.op(topLeftArea, Region.Op.UNION);
+ tapExcludeRegion.op(topRightArea, Region.Op.UNION);
+ tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION);
+ tapExcludeRegion.op(bottomRightArea, Region.Op.UNION);
+ tapExcludeRegion.op(closeArea, Region.Op.UNION);
+
+ regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE);
+
return regionInsideDragBorder;
}
@@ -756,6 +789,10 @@
mRightDrag = mMirrorView.findViewById(R.id.right_handle);
mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
mCloseView = mMirrorView.findViewById(R.id.close_button);
+ mTopRightCornerView = mMirrorView.findViewById(R.id.top_right_corner);
+ mTopLeftCornerView = mMirrorView.findViewById(R.id.top_left_corner);
+ mBottomRightCornerView = mMirrorView.findViewById(R.id.bottom_right_corner);
+ mBottomLeftCornerView = mMirrorView.findViewById(R.id.bottom_left_corner);
mDragView.setOnTouchListener(this);
mLeftDrag.setOnTouchListener(this);
@@ -763,6 +800,10 @@
mRightDrag.setOnTouchListener(this);
mBottomDrag.setOnTouchListener(this);
mCloseView.setOnTouchListener(this);
+ mTopLeftCornerView.setOnTouchListener(this);
+ mTopRightCornerView.setOnTouchListener(this);
+ mBottomLeftCornerView.setOnTouchListener(this);
+ mBottomRightCornerView.setOnTouchListener(this);
}
/**
@@ -831,8 +872,16 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
- if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
- || v == mBottomDrag || v == mCloseView) {
+ if (v == mDragView
+ || v == mLeftDrag
+ || v == mTopDrag
+ || v == mRightDrag
+ || v == mBottomDrag
+ || v == mTopLeftCornerView
+ || v == mTopRightCornerView
+ || v == mBottomLeftCornerView
+ || v == mBottomRightCornerView
+ || v == mCloseView) {
return mGestureDetector.onTouch(v, event);
}
return false;
@@ -1195,7 +1244,7 @@
@Override
public boolean onDrag(View view, float offsetX, float offsetY) {
if (mEditSizeEnable) {
- changeWindowSize(view, offsetX, offsetY);
+ return changeWindowSize(view, offsetX, offsetY);
} else {
move((int) offsetX, (int) offsetY);
}
@@ -1220,13 +1269,47 @@
if (mEditSizeEnable) {
mDragView.setVisibility(View.GONE);
mCloseView.setVisibility(View.VISIBLE);
+ mTopRightCornerView.setVisibility(View.VISIBLE);
+ mTopLeftCornerView.setVisibility(View.VISIBLE);
+ mBottomRightCornerView.setVisibility(View.VISIBLE);
+ mBottomLeftCornerView.setVisibility(View.VISIBLE);
} else {
mDragView.setVisibility(View.VISIBLE);
mCloseView.setVisibility(View.GONE);
+ mTopRightCornerView.setVisibility(View.GONE);
+ mTopLeftCornerView.setVisibility(View.GONE);
+ mBottomRightCornerView.setVisibility(View.GONE);
+ mBottomLeftCornerView.setVisibility(View.GONE);
}
}
- public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+ private boolean changeWindowSize(View view, float offsetX, float offsetY) {
+ if (view == mLeftDrag) {
+ changeMagnificationFrameSize(offsetX, 0, 0, 0);
+ } else if (view == mRightDrag) {
+ changeMagnificationFrameSize(0, 0, offsetX, 0);
+ } else if (view == mTopDrag) {
+ changeMagnificationFrameSize(0, offsetY, 0, 0);
+ } else if (view == mBottomDrag) {
+ changeMagnificationFrameSize(0, 0, 0, offsetY);
+ } else if (view == mTopLeftCornerView) {
+ changeMagnificationFrameSize(offsetX, offsetY, 0, 0);
+ } else if (view == mTopRightCornerView) {
+ changeMagnificationFrameSize(0, offsetY, offsetX, 0);
+ } else if (view == mBottomLeftCornerView) {
+ changeMagnificationFrameSize(offsetX, 0, 0, offsetY);
+ } else if (view == mBottomRightCornerView) {
+ changeMagnificationFrameSize(0, 0, offsetX, offsetY);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void changeMagnificationFrameSize(
+ float leftOffset, float topOffset, float rightOffset,
+ float bottomOffset) {
boolean bRTL = isRTL(mContext);
final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
@@ -1236,54 +1319,26 @@
Rect tempRect = new Rect();
tempRect.set(mMagnificationFrame);
- if (view == mLeftDrag) {
- if (bRTL) {
- tempRect.right += offsetX;
- if (tempRect.right > mWindowBounds.width()) {
- return false;
- }
- } else {
- tempRect.left += offsetX;
- if (tempRect.left < 0) {
- return false;
- }
- }
- } else if (view == mRightDrag) {
- if (bRTL) {
- tempRect.left += offsetX;
- if (tempRect.left < 0) {
- return false;
- }
- } else {
- tempRect.right += offsetX;
- if (tempRect.right > mWindowBounds.width()) {
- return false;
- }
- }
- } else if (view == mTopDrag) {
- tempRect.top += offsetY;
- if (tempRect.top < 0) {
- return false;
- }
- } else if (view == mBottomDrag) {
- tempRect.bottom += offsetY;
- if (tempRect.bottom > mWindowBounds.height()) {
- return false;
- }
+ if (bRTL) {
+ tempRect.left += (int) (rightOffset);
+ tempRect.right += (int) (leftOffset);
+ } else {
+ tempRect.right += (int) (rightOffset);
+ tempRect.left += (int) (leftOffset);
}
+ tempRect.top += (int) (topOffset);
+ tempRect.bottom += (int) (bottomOffset);
if (tempRect.width() < initSize || tempRect.height() < initSize
|| tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
- return false;
+ return;
}
-
mMagnificationFrame.set(tempRect);
computeBounceAnimationScale();
calculateMagnificationFrameBoundary();
modifyWindowMagnification(true);
- return true;
}
private static boolean isRTL(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9cffd5d..069c0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -103,7 +103,7 @@
MagnificationSize.LARGE,
})
/** Denotes the Magnification size type. */
- @interface MagnificationSize {
+ public @interface MagnificationSize {
int NONE = 0;
int SMALL = 1;
int MEDIUM = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
index 9af8300..de351ec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -57,7 +57,7 @@
@FloatRange(from = 0.0, to = 1.0)
private static final float DEFAULT_POSITION_X_PERCENT = 1.0f;
@FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_POSITION_Y_PERCENT = 0.9f;
+ private static final float DEFAULT_POSITION_Y_PERCENT = 0.77f;
private final Context mContext;
private final AccessibilityFloatingMenuView mMenuView;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 396f584..1e14763 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -16,8 +16,6 @@
package com.android.systemui.accessibility.floatingmenu;
-import static android.util.MathUtils.constrain;
-
import static java.util.Objects.requireNonNull;
import android.animation.ValueAnimator;
@@ -64,7 +62,6 @@
private final MenuView mMenuView;
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
- private boolean mIsMovedToEdge;
private boolean mIsFadeEffectEnabled;
private DismissAnimationController.DismissCallback mDismissCallback;
@@ -111,25 +108,25 @@
}
void moveToTopLeftPosition() {
- mIsMovedToEdge = false;
+ mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.top));
}
void moveToTopRightPosition() {
- mIsMovedToEdge = false;
+ mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.top));
}
void moveToBottomLeftPosition() {
- mIsMovedToEdge = false;
+ mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.bottom));
}
void moveToBottomRightPosition() {
- mIsMovedToEdge = false;
+ mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.bottom));
}
@@ -254,6 +251,8 @@
// If the translation x is zero, it should be at the left of the bound.
if (currentXTranslation < draggableBounds.left
|| currentXTranslation > draggableBounds.right) {
+ constrainPositionAndUpdate(
+ new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()));
moveToEdgeAndHide();
return true;
}
@@ -262,37 +261,33 @@
return false;
}
- private boolean isOnLeftSide() {
+ boolean isOnLeftSide() {
return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX();
}
- boolean isMovedToEdge() {
- return mIsMovedToEdge;
+ boolean isMoveToTucked() {
+ return mMenuView.isMoveToTucked();
}
void moveToEdgeAndHide() {
- mIsMovedToEdge = true;
+ mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
- final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
- final float endY = constrain(mMenuView.getTranslationY(), draggableBounds.top,
- draggableBounds.bottom);
- final float menuHalfWidth = mMenuView.getWidth() / 2.0f;
+ final PointF position = mMenuView.getMenuPosition();
+ final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f;
final float endX = isOnLeftSide()
- ? draggableBounds.left - menuHalfWidth
- : draggableBounds.right + menuHalfWidth;
- moveAndPersistPosition(new PointF(endX, endY));
+ ? position.x - menuHalfWidth
+ : position.x + menuHalfWidth;
+ moveToPosition(new PointF(endX, position.y));
// Keep the touch region let users could click extra space to pop up the menu view
// from the screen edge
- mMenuView.onBoundsInParentChanged(isOnLeftSide()
- ? draggableBounds.left
- : draggableBounds.right, (int) mMenuView.getTranslationY());
+ mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
fadeOutIfEnabled();
}
void moveOutEdgeAndShow() {
- mIsMovedToEdge = false;
+ mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
mMenuView.onPositionChanged();
mMenuView.onEdgeChangedIfNeeded();
@@ -345,7 +340,7 @@
}
private void constrainPositionAndUpdate(PointF position) {
- final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme();
// Have the space gap margin between the top bound and the menu view, so actually the
// position y range needs to cut the margin.
position.offset(-draggableBounds.left, -draggableBounds.top);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 57019de..5bc7406 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -50,7 +50,8 @@
private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f;
@FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.9f;
+ private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f;
+ private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false;
private final Context mContext;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -92,6 +93,12 @@
mPercentagePosition = getStartPosition();
}
+ void loadMenuMoveToTucked(OnInfoReady<Boolean> callback) {
+ callback.onReady(
+ Prefs.getBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+ DEFAULT_MOVE_TO_TUCKED_VALUE));
+ }
+
void loadMenuPosition(OnInfoReady<Position> callback) {
callback.onReady(mPercentagePosition);
}
@@ -113,6 +120,11 @@
getMenuOpacityFromSettings(mContext));
}
+ void updateMoveToTucked(boolean isMoveToTucked) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+ isMoveToTucked);
+ }
+
void updateMenuSavingPosition(Position percentagePosition) {
mPercentagePosition = percentagePosition;
Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index ac5736b..14517ba 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -74,10 +74,10 @@
R.string.accessibility_floating_button_action_move_bottom_right));
info.addAction(moveBottomRight);
- final int moveEdgeId = mAnimationController.isMovedToEdge()
+ final int moveEdgeId = mAnimationController.isMoveToTucked()
? R.id.action_move_out_edge_and_show
: R.id.action_move_to_edge_and_hide;
- final int moveEdgeTextResId = mAnimationController.isMovedToEdge()
+ final int moveEdgeTextResId = mAnimationController.isMoveToTucked()
? R.string.accessibility_floating_button_action_move_out_edge_and_show
: R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half;
final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge =
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 6a14af5..986aa51 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -58,12 +58,15 @@
this::updateSystemGestureExcludeRects;
private final Observer<MenuFadeEffectInfo> mFadeEffectInfoObserver =
this::onMenuFadeEffectInfoChanged;
+ private final Observer<Boolean> mMoveToTuckedObserver = this::onMoveToTucked;
private final Observer<Position> mPercentagePositionObserver = this::onPercentagePosition;
private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged;
private final Observer<List<AccessibilityTarget>> mTargetFeaturesObserver =
this::onTargetFeaturesChanged;
private final MenuViewAppearance mMenuViewAppearance;
+ private boolean mIsMoveToTucked;
+
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
@@ -161,6 +164,12 @@
mMenuViewAppearance.getMenuStrokeColor());
}
+ private void onMoveToTucked(boolean isMoveToTucked) {
+ mIsMoveToTucked = isMoveToTucked;
+
+ onPositionChanged();
+ }
+
private void onPercentagePosition(Position percentagePosition) {
mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -171,6 +180,10 @@
final PointF position = mMenuViewAppearance.getMenuPosition();
mMenuAnimationController.moveToPosition(position);
onBoundsInParentChanged((int) position.x, (int) position.y);
+
+ if (isMoveToTucked()) {
+ mMenuAnimationController.moveToEdgeAndHide();
+ }
}
@SuppressLint("NotifyDataSetChanged")
@@ -219,6 +232,22 @@
return mMenuViewAppearance.getMenuDraggableBounds();
}
+ Rect getMenuDraggableBoundsExcludeIme() {
+ return mMenuViewAppearance.getMenuDraggableBoundsExcludeIme();
+ }
+
+ int getMenuHeight() {
+ return mMenuViewAppearance.getMenuHeight();
+ }
+
+ int getMenuWidth() {
+ return mMenuViewAppearance.getMenuWidth();
+ }
+
+ PointF getMenuPosition() {
+ return mMenuViewAppearance.getMenuPosition();
+ }
+
void persistPositionAndUpdateEdge(Position percentagePosition) {
mMenuViewModel.updateMenuSavingPosition(percentagePosition);
mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -226,6 +255,16 @@
onEdgeChangedIfNeeded();
}
+ boolean isMoveToTucked() {
+ return mIsMoveToTucked;
+ }
+
+ void updateMenuMoveToTucked(boolean isMoveToTucked) {
+ mIsMoveToTucked = isMoveToTucked;
+ mMenuViewModel.updateMenuMoveToTucked(isMoveToTucked);
+ }
+
+
/**
* Uses the touch events from the parent view to identify if users clicked the extra
* space of the menu view. If yes, will use the percentage position and update the
@@ -241,7 +280,7 @@
boolean maybeMoveOutEdgeAndShow(int x, int y) {
// Utilizes the touch region of the parent view to implement that users could tap extra
// the space region to show the menu from the edge.
- if (!mMenuAnimationController.isMovedToEdge() || !mBoundsInParent.contains(x, y)) {
+ if (!isMoveToTucked() || !mBoundsInParent.contains(x, y)) {
return false;
}
@@ -258,6 +297,7 @@
mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver);
mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver);
mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver);
+ mMenuViewModel.getMoveToTuckedData().observeForever(mMoveToTuckedObserver);
setVisibility(VISIBLE);
mMenuViewModel.registerContentObservers();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -271,6 +311,7 @@
mMenuViewModel.getFadeEffectInfoData().removeObserver(mFadeEffectInfoObserver);
mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver);
mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver);
+ mMenuViewModel.getMoveToTuckedData().removeObserver(mMoveToTuckedObserver);
mMenuViewModel.unregisterContentObservers();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4a9807f..a7cdeab 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -47,6 +47,9 @@
private final Resources mRes;
private final Position mPercentagePosition = new Position(/* percentageX= */
0f, /* percentageY= */ 0f);
+ private boolean mIsImeShowing;
+ // Avoid the menu view overlapping on the primary action button under the bottom as possible.
+ private int mImeShiftingSpace;
private int mTargetFeaturesSize;
private int mSizeType;
private int mMargin;
@@ -62,6 +65,7 @@
private int mStrokeColor;
private int mInset;
private int mElevation;
+ private float mImeTop;
private float[] mRadii;
private Drawable mBackgroundDrawable;
private String mContentDescription;
@@ -106,6 +110,8 @@
mStrokeColor = mRes.getColor(R.color.accessibility_floating_menu_stroke_dark);
mInset = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
mElevation = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
+ mImeShiftingSpace = mRes.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_ime_shifting_space);
final Drawable drawable =
mRes.getDrawable(R.drawable.accessibility_floating_menu_background);
mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable});
@@ -131,29 +137,56 @@
mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize));
}
+ void onImeVisibilityChanged(boolean imeShowing, float imeTop) {
+ mIsImeShowing = imeShowing;
+ mImeTop = imeTop;
+ }
+
Rect getMenuDraggableBounds() {
+ return getMenuDraggableBoundsWith(/* includeIme= */ true);
+ }
+
+ Rect getMenuDraggableBoundsExcludeIme() {
+ return getMenuDraggableBoundsWith(/* includeIme= */ false);
+ }
+
+ private Rect getMenuDraggableBoundsWith(boolean includeIme) {
final int margin = getMenuMargin();
- final Rect draggableBounds = getWindowAvailableBounds();
+ final Rect draggableBounds = new Rect(getWindowAvailableBounds());
// Initializes start position for mapping the translation of the menu view.
draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
draggableBounds.top += margin;
draggableBounds.right -= getMenuWidth();
- draggableBounds.bottom -= Math.min(
- getWindowAvailableBounds().height() - draggableBounds.top,
- calculateActualMenuHeight() + margin);
+
+ if (includeIme && mIsImeShowing) {
+ final int imeHeight = (int) (draggableBounds.bottom - mImeTop);
+ draggableBounds.bottom -= (imeHeight + mImeShiftingSpace);
+ }
+ draggableBounds.bottom -= (calculateActualMenuHeight() + margin);
+ draggableBounds.bottom = Math.max(draggableBounds.top, draggableBounds.bottom);
+
return draggableBounds;
}
PointF getMenuPosition() {
- final Rect draggableBounds = getMenuDraggableBounds();
+ final Rect draggableBounds = getMenuDraggableBoundsExcludeIme();
+ final float x = draggableBounds.left
+ + draggableBounds.width() * mPercentagePosition.getPercentageX();
- return new PointF(
- draggableBounds.left
- + draggableBounds.width() * mPercentagePosition.getPercentageX(),
- draggableBounds.top
- + draggableBounds.height() * mPercentagePosition.getPercentageY());
+ float y = draggableBounds.top
+ + draggableBounds.height() * mPercentagePosition.getPercentageY();
+
+ // If the bottom of the menu view and overlap on the ime, its position y will be
+ // overridden with new y.
+ final float menuBottom = y + getMenuHeight() + mMargin;
+ if (mIsImeShowing && (menuBottom >= mImeTop)) {
+ y = Math.max(draggableBounds.top,
+ mImeTop - getMenuHeight() - mMargin - mImeShiftingSpace);
+ }
+
+ return new PointF(x, y);
}
String getContentDescription() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 33e155d..c42943c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,18 +16,30 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.view.WindowInsets.Type.ime;
+
+import static androidx.core.view.WindowInsetsCompat.Type;
+
+import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
+import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
+import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -57,14 +69,17 @@
class MenuViewLayer extends FrameLayout {
private static final int SHOW_MESSAGE_DELAY_MS = 3000;
+ private final WindowManager mWindowManager;
private final MenuView mMenuView;
private final MenuMessageView mMessageView;
private final DismissView mDismissView;
+ private final MenuViewAppearance mMenuViewAppearance;
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final DismissAnimationController mDismissAnimationController;
+ private final Rect mImeInsetsRect = new Rect();
@IntDef({
LayerIndex.MENU_VIEW,
@@ -82,8 +97,22 @@
final Runnable mDismissMenuAction = new Runnable() {
@Override
public void run() {
- Settings.Secure.putString(getContext().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+ Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+ UserHandle.USER_CURRENT);
+
+ // Should disable the corresponding service when the fragment type is
+ // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
+ final List<AccessibilityServiceInfo> serviceInfoList =
+ mAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ serviceInfoList.forEach(info -> {
+ if (getAccessibilityServiceFragmentType(info) == INVISIBLE_TOGGLE) {
+ setAccessibilityServiceState(mContext, info.getComponentName(), /* enabled= */
+ false);
+ }
+ });
+
mFloatingMenu.hide();
}
};
@@ -92,13 +121,13 @@
AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
super(context);
+ mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
mFloatingMenu = floatingMenu;
final MenuViewModel menuViewModel = new MenuViewModel(context);
- final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
- windowManager);
- mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
+ mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
+ mMenuView = new MenuView(context, menuViewModel, mMenuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
@@ -181,6 +210,7 @@
super.onAttachedToWindow();
mMenuView.show();
+ setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
mMessageView.setUndoListener(view -> undo());
mContext.registerComponentCallbacks(mDismissAnimationController);
}
@@ -190,10 +220,35 @@
super.onDetachedFromWindow();
mMenuView.hide();
+ setOnApplyWindowInsetsListener(null);
mHandler.removeCallbacksAndMessages(/* token= */ null);
mContext.unregisterComponentCallbacks(mDismissAnimationController);
}
+ private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
+ final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ final WindowInsets windowInsets = windowMetrics.getWindowInsets();
+ final Rect imeInsetsRect = windowInsets.getInsets(ime()).toRect();
+ if (!imeInsetsRect.equals(mImeInsetsRect)) {
+ final Rect windowBounds = new Rect(windowMetrics.getBounds());
+ final Rect systemBarsAndDisplayCutoutInsetsRect =
+ windowInsets.getInsetsIgnoringVisibility(
+ Type.systemBars() | Type.displayCutout()).toRect();
+ final float imeTop =
+ windowBounds.height() - systemBarsAndDisplayCutoutInsetsRect.top
+ - imeInsetsRect.bottom;
+
+ mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop);
+
+ mMenuView.onEdgeChanged();
+ mMenuView.onPositionChanged();
+
+ mImeInsetsRect.set(imeInsetsRect);
+ }
+
+ return insets;
+ }
+
private void hideMenuAndShowMessage() {
final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
SHOW_MESSAGE_DELAY_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index e8a2b6e..bd41787 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -35,6 +35,7 @@
private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>();
private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
new MutableLiveData<>();
+ private final MutableLiveData<Boolean> mMoveToTuckedData = new MutableLiveData<>();
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
private final MenuInfoRepository mInfoRepository;
@@ -57,10 +58,19 @@
mFadeEffectInfoData.setValue(fadeEffectInfo);
}
+ void updateMenuMoveToTucked(boolean isMoveToTucked) {
+ mInfoRepository.updateMoveToTucked(isMoveToTucked);
+ }
+
void updateMenuSavingPosition(Position percentagePosition) {
mInfoRepository.updateMenuSavingPosition(percentagePosition);
}
+ LiveData<Boolean> getMoveToTuckedData() {
+ mInfoRepository.loadMenuMoveToTucked(mMoveToTuckedData::setValue);
+ return mMoveToTuckedData;
+ }
+
LiveData<Position> getPercentagePositionData() {
mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue);
return mPercentagePositionData;
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
new file mode 100644
index 0000000..b52ddc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.battery
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.DrawableWrapper
+import android.util.PathParser
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
+import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
+import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET
+
+/**
+ * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
+ * necessary.
+ *
+ * For now, it adds a shield in the bottom-right corner when [displayShield] is true.
+ */
+class AccessorizedBatteryDrawable(
+ private val context: Context,
+ frameColor: Int,
+) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
+ private val mainBatteryDrawable: ThemedBatteryDrawable
+ get() = drawable as ThemedBatteryDrawable
+
+ private val shieldPath = Path()
+ private val scaledShield = Path()
+ private val scaleMatrix = Matrix()
+
+ private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
+ private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET
+
+ private var density = context.resources.displayMetrics.density
+
+ private val dualTone =
+ context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)
+
+ private val shieldTransparentOutlinePaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.TRANSPARENT
+ p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+ p.style = Paint.Style.FILL_AND_STROKE
+ }
+
+ private val shieldPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.MAGENTA
+ p.style = Paint.Style.FILL
+ p.isDither = true
+ }
+
+ init {
+ loadPaths()
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ updateSizes()
+ }
+
+ var displayShield: Boolean = false
+
+ private fun updateSizes() {
+ val b = bounds
+ if (b.isEmpty) {
+ return
+ }
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
+ val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)
+
+ drawable?.setBounds(
+ b.left,
+ b.top,
+ /* right= */ b.left + mainWidth.toInt(),
+ /* bottom= */ b.top + mainHeight.toInt()
+ )
+
+ if (displayShield) {
+ val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
+ val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
+ scaleMatrix.setScale(sx, sy)
+ shieldPath.transform(scaleMatrix, scaledShield)
+
+ shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
+ shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET
+
+ val scaledStrokeWidth =
+ (sx * SHIELD_STROKE).coerceAtLeast(
+ ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ )
+ shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
+ }
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ val height =
+ if (displayShield) {
+ BATTERY_HEIGHT_WITH_SHIELD
+ } else {
+ BATTERY_HEIGHT
+ }
+ return (height * density).toInt()
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ val width =
+ if (displayShield) {
+ BATTERY_WIDTH_WITH_SHIELD
+ } else {
+ BATTERY_WIDTH
+ }
+ return (width * density).toInt()
+ }
+
+ override fun draw(c: Canvas) {
+ c.saveLayer(null, null)
+ // Draw the main battery icon
+ super.draw(c)
+
+ if (displayShield) {
+ c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
+ // We need a transparent outline around the shield, so first draw the transparent-ness
+ // then draw the shield
+ c.drawPath(scaledShield, shieldTransparentOutlinePaint)
+ c.drawPath(scaledShield, shieldPaint)
+ }
+ c.restore()
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.OPAQUE
+ }
+
+ override fun setAlpha(p0: Int) {
+ // Unused internally -- see [ThemedBatteryDrawable.setAlpha].
+ }
+
+ override fun setColorFilter(colorfilter: ColorFilter?) {
+ super.setColorFilter(colorFilter)
+ shieldPaint.colorFilter = colorFilter
+ }
+
+ /** Sets whether the battery is currently charging. */
+ fun setCharging(charging: Boolean) {
+ mainBatteryDrawable.charging = charging
+ }
+
+ /** Sets the current level (out of 100) of the battery. */
+ fun setBatteryLevel(level: Int) {
+ mainBatteryDrawable.setBatteryLevel(level)
+ }
+
+ /** Sets whether power save is enabled. */
+ fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
+ mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
+ }
+
+ /** Returns whether power save is currently enabled. */
+ fun getPowerSaveEnabled(): Boolean {
+ return mainBatteryDrawable.powerSaveEnabled
+ }
+
+ /** Sets the colors to use for the icon. */
+ fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
+ shieldPaint.color = if (dualTone) fgColor else singleToneColor
+ mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
+ }
+
+ /** Notifies this drawable that the density might have changed. */
+ fun notifyDensityChanged() {
+ density = context.resources.displayMetrics.density
+ }
+
+ private fun loadPaths() {
+ val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
+ shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 6a10d4a..03d999f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,7 +45,6 @@
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
-import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -68,7 +67,7 @@
public static final int MODE_OFF = 2;
public static final int MODE_ESTIMATE = 3;
- private final ThemedBatteryDrawable mDrawable;
+ private final AccessorizedBatteryDrawable mDrawable;
private final ImageView mBatteryIconView;
private TextView mBatteryPercentView;
@@ -77,7 +76,10 @@
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
private boolean mShowPercentAvailable;
+ private String mEstimateText = null;
private boolean mCharging;
+ private boolean mIsOverheated;
+ private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
// Lazily-loaded since this is expected to be a rare-if-ever state
@@ -106,7 +108,7 @@
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.meter_background_color));
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
- mDrawable = new ThemedBatteryDrawable(context, frameColor);
+ mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
atts.recycle();
mShowPercentAvailable = context.getResources().getBoolean(
@@ -170,12 +172,14 @@
if (mode == mShowPercentMode) return;
mShowPercentMode = mode;
updateShowPercent();
+ updatePercentText();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updatePercentView();
+ mDrawable.notifyDensityChanged();
}
public void setColorsFromContext(Context context) {
@@ -203,6 +207,17 @@
mDrawable.setPowerSaveEnabled(isPowerSave);
}
+ void onIsOverheatedChanged(boolean isOverheated) {
+ boolean valueChanged = mIsOverheated != isOverheated;
+ mIsOverheated = isOverheated;
+ if (valueChanged) {
+ updateContentDescription();
+ // The battery drawable is a different size depending on whether it's currently
+ // overheated or not, so we need to re-scale the view when overheated changes.
+ scaleBatteryMeterViews();
+ }
+ }
+
private TextView loadPercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
@@ -227,13 +242,17 @@
mBatteryEstimateFetcher = fetcher;
}
+ void setDisplayShieldEnabled(boolean displayShieldEnabled) {
+ mDisplayShieldEnabled = displayShieldEnabled;
+ }
+
void updatePercentText() {
if (mBatteryStateUnknown) {
- setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
return;
}
if (mBatteryEstimateFetcher == null) {
+ setPercentTextAtCurrentLevel();
return;
}
@@ -245,10 +264,9 @@
return;
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+ mEstimateText = estimate;
mBatteryPercentView.setText(estimate);
- setContentDescription(getContext().getString(
- R.string.accessibility_battery_level_with_estimate,
- mLevel, estimate));
+ updateContentDescription();
} else {
setPercentTextAtCurrentLevel();
}
@@ -257,28 +275,49 @@
setPercentTextAtCurrentLevel();
}
} else {
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ updateContentDescription();
}
}
private void setPercentTextAtCurrentLevel() {
- if (mBatteryPercentView == null) {
- return;
+ if (mBatteryPercentView != null) {
+ mEstimateText = null;
+ String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
+ // Setting text actually triggers a layout pass (because the text view is set to
+ // wrap_content width and TextView always relayouts for this). Avoid needless
+ // relayout if the text didn't actually change.
+ if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
+ mBatteryPercentView.setText(percentText);
+ }
}
- String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
- // Setting text actually triggers a layout pass (because the text view is set to
- // wrap_content width and TextView always relayouts for this). Avoid needless
- // relayout if the text didn't actually change.
- if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
- mBatteryPercentView.setText(percentText);
+ updateContentDescription();
+ }
+
+ private void updateContentDescription() {
+ Context context = getContext();
+
+ String contentDescription;
+ if (mBatteryStateUnknown) {
+ contentDescription = context.getString(R.string.accessibility_battery_unknown);
+ } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
+ contentDescription = context.getString(
+ mIsOverheated
+ ? R.string.accessibility_battery_level_charging_paused_with_estimate
+ : R.string.accessibility_battery_level_with_estimate,
+ mLevel,
+ mEstimateText);
+ } else if (mIsOverheated) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
+ } else if (mCharging) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging, mLevel);
+ } else {
+ contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
}
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ setContentDescription(contentDescription);
}
void updateShowPercent() {
@@ -329,6 +368,7 @@
}
mBatteryStateUnknown = isUnknown;
+ updateContentDescription();
if (mBatteryStateUnknown) {
mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
@@ -349,15 +389,43 @@
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
float iconScaleFactor = typedValue.getFloat();
- int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
- int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
+ float mainBatteryHeight =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
+ float mainBatteryWidth =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
+
+ // If the battery is marked as overheated, we should display a shield indicating that the
+ // battery is being "defended".
+ boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
+ float fullBatteryIconHeight =
+ BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
+ float fullBatteryIconWidth =
+ BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);
+
+ int marginTop;
+ if (displayShield) {
+ // If the shield is displayed, we need some extra marginTop so that the bottom of the
+ // main icon is still aligned with the bottom of all the other system icons.
+ int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
+ // However, the other system icons have some embedded bottom padding that the battery
+ // doesn't have, so we shouldn't move the battery icon down by the full amount.
+ // See b/258672854.
+ marginTop = shieldHeightAddition
+ - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
+ } else {
+ marginTop = 0;
+ }
+
int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
- (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
- scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
+ Math.round(fullBatteryIconWidth),
+ Math.round(fullBatteryIconHeight));
+ scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
+ mDrawable.setDisplayShield(displayShield);
mBatteryIconView.setLayoutParams(scaledLayoutParams);
+ mBatteryIconView.invalidateDrawable(mDrawable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index ae9a323..f4ec33a 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -17,19 +17,23 @@
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
-import android.app.ActivityManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import androidx.annotation.NonNull;
+
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -42,12 +46,13 @@
public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
+ private final Handler mMainHandler;
private final ContentResolver mContentResolver;
private final BatteryController mBatteryController;
private final String mSlotBattery;
private final SettingObserver mSettingObserver;
- private final CurrentUserTracker mCurrentUserTracker;
+ private final UserTracker mUserTracker;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -84,6 +89,21 @@
public void onBatteryUnknownStateChanged(boolean isUnknown) {
mView.onBatteryUnknownStateChanged(isUnknown);
}
+
+ @Override
+ public void onIsOverheatedChanged(boolean isOverheated) {
+ mView.onIsOverheatedChanged(isOverheated);
+ }
+ };
+
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mContentResolver.unregisterContentObserver(mSettingObserver);
+ registerShowBatteryPercentObserver(newUser);
+ mView.updateShowPercent();
+ }
};
// Some places may need to show the battery conditionally, and not obey the tuner
@@ -93,30 +113,26 @@
@Inject
public BatteryMeterViewController(
BatteryMeterView view,
+ UserTracker userTracker,
ConfigurationController configurationController,
TunerService tunerService,
- BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController) {
super(view);
+ mUserTracker = userTracker;
mConfigurationController = configurationController;
mTunerService = tunerService;
+ mMainHandler = mainHandler;
mContentResolver = contentResolver;
mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
+ mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
- mSettingObserver = new SettingObserver(mainHandler);
- mCurrentUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- contentResolver.unregisterContentObserver(mSettingObserver);
- registerShowBatteryPercentObserver(newUserId);
- mView.updateShowPercent();
- }
- };
+ mSettingObserver = new SettingObserver(mMainHandler);
}
@Override
@@ -125,9 +141,9 @@
subscribeForTunerUpdates();
mBatteryController.addCallback(mBatteryStateChangeCallback);
- registerShowBatteryPercentObserver(ActivityManager.getCurrentUser());
+ registerShowBatteryPercentObserver(mUserTracker.getUserId());
registerGlobalBatteryUpdateObserver();
- mCurrentUserTracker.startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
mView.updateShowPercent();
}
@@ -138,7 +154,7 @@
unsubscribeFromTunerUpdates();
mBatteryController.removeCallback(mBatteryStateChangeCallback);
- mCurrentUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
mContentResolver.unregisterContentObserver(mSettingObserver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
new file mode 100644
index 0000000..6455a96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.battery
+
+import com.android.settingslib.graph.ThemedBatteryDrawable
+
+/** An object storing specs related to the battery icon in the status bar. */
+object BatterySpecs {
+
+ /** Width of the main battery icon, not including the shield. */
+ const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH
+ /** Height of the main battery icon, not including the shield. */
+ const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT
+
+ private const val SHIELD_WIDTH = 10f
+ private const val SHIELD_HEIGHT = 13f
+
+ /**
+ * Amount that the left side of the shield should be offset from the left side of the battery.
+ */
+ const val SHIELD_LEFT_OFFSET = 8f
+ /** Amount that the top of the shield should be offset from the top of the battery. */
+ const val SHIELD_TOP_OFFSET = 10f
+
+ const val SHIELD_STROKE = 4f
+
+ /** The full width of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH
+ /** The full height of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT
+
+ /**
+ * Given the desired height of the main battery icon in pixels, returns the height that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield
+ * extends slightly below the bottom of the main battery icon so we need some extra height.
+ */
+ @JvmStatic
+ fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryHeight
+ } else {
+ val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT
+ verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the desired width of the main battery icon in pixels, returns the width that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends
+ * past the right side of the main battery icon so we need some extra width.
+ */
+ @JvmStatic
+ fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryWidth
+ } else {
+ val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH
+ horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the height of the full battery icon, return how tall the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes
+ * up some of the view's height so the main battery width will be just a portion of
+ * [fullBatteryHeight].
+ */
+ @JvmStatic
+ fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryHeight
+ } else {
+ return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight
+ }
+ }
+
+ /**
+ * Given the width of the full battery icon, return how wide the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes
+ * up some of the view's width so the main battery width will be just a portion of
+ * [fullBatteryWidth].
+ */
+ @JvmStatic
+ fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryWidth
+ } else {
+ return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f74c721..815ac68 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -141,8 +141,7 @@
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
private final @Background DelayableExecutor mBackgroundExecutor;
- private int mOrientation;
- private boolean mSkipFirstLostFocus = false;
+ private boolean mIsOrientationChanged = false;
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
@Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
@@ -491,6 +490,7 @@
@Override
public void onOrientationChanged() {
maybeUpdatePositionForUdfps(true /* invalidate */);
+ mIsOrientationChanged = true;
}
@Override
@@ -499,8 +499,8 @@
if (!hasWindowFocus) {
//it's a workaround to avoid closing BP incorrectly
//BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true)
- if (mSkipFirstLostFocus) {
- mSkipFirstLostFocus = false;
+ if (mIsOrientationChanged) {
+ mIsOrientationChanged = false;
return;
}
Log.v(TAG, "Lost window focus, dismissing the dialog");
@@ -512,9 +512,6 @@
public void onAttachedToWindow() {
super.onAttachedToWindow();
- //save the first orientation
- mOrientation = getResources().getConfiguration().orientation;
-
mWakefulnessLifecycle.addObserver(this);
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
@@ -670,7 +667,7 @@
}
if (savedState != null) {
- mSkipFirstLostFocus = savedState.getBoolean(
+ mIsOrientationChanged = savedState.getBoolean(
AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED);
}
@@ -764,9 +761,7 @@
mBiometricView != null && mCredentialView == null);
outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
- if (mOrientation != getResources().getConfiguration().orientation) {
- outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, true);
- }
+ outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, mIsOrientationChanged);
if (mBiometricView != null) {
mBiometricView.onSaveState(outState);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index c93fe6a..4b57d45 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,7 +29,7 @@
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators
-import com.android.systemui.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
new file mode 100644
index 0000000..794eba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+ "presubmit": [
+ {
+ // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+ "name": "SystemUIGoogleBiometricsScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index ad96612..bdad413 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -37,6 +37,9 @@
private float mDialogSuggestedAlpha = 1f;
private float mNotificationShadeExpansion = 0f;
+ // Used for Udfps ellipse detection when flag is true, set by AnimationViewController
+ boolean mUseExpandedOverlay = false;
+
// mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha
private int mAlpha;
boolean mPauseAuth;
@@ -118,6 +121,24 @@
}
/**
+ * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+ *
+ * @param bounds RectF based off screen coordinates in current orientation
+ */
+ RectF getBoundsRelativeToView(RectF bounds) {
+ int[] pos = getLocationOnScreen();
+
+ RectF output = new RectF(
+ bounds.left - pos[0],
+ bounds.top - pos[1],
+ bounds.right - pos[0],
+ bounds.bottom - pos[1]
+ );
+
+ return output;
+ }
+
+ /**
* Set the suggested alpha based on whether a dialog was recently shown or hidden.
* @param dialogSuggestedAlpha value from 0f to 1f.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3631057..1d4281f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -69,7 +69,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -142,7 +142,7 @@
@NonNull private final LatencyTracker mLatencyTracker;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
- @NonNull private final BouncerInteractor mBouncerInteractor;
+ @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -166,6 +166,7 @@
// The current request from FingerprintService. Null if no current request.
@Nullable UdfpsControllerOverlay mOverlay;
+ @Nullable private UdfpsEllipseDetection mUdfpsEllipseDetection;
// The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when
// to turn off high brightness mode. To get around this limitation, the state of the AOD
@@ -235,7 +236,7 @@
mUdfpsDisplayMode, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
- mBouncerInteractor)));
+ mPrimaryBouncerInteractor)));
}
@Override
@@ -354,14 +355,18 @@
if (!mOverlayParams.equals(overlayParams)) {
mOverlayParams = overlayParams;
- final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ mUdfpsEllipseDetection.updateOverlayParams(overlayParams);
+ }
+
+ final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer();
// When the bounds change it's always necessary to re-create the overlay's window with
// new LayoutParams. If the overlay needs to be shown, this will re-create and show the
// overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden.
redrawOverlay();
if (wasShowingAltAuth) {
- mKeyguardViewManager.showGenericBouncer(true);
+ mKeyguardViewManager.showBouncer(true);
}
}
}
@@ -493,8 +498,23 @@
mVelocityTracker.clear();
}
- boolean withinSensorArea =
+ boolean withinSensorArea;
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ // Ellipse detection
+ withinSensorArea = mUdfpsEllipseDetection.isGoodEllipseOverlap(event);
+ } else {
+ // Centroid with expanded overlay
+ withinSensorArea =
+ isWithinSensorArea(udfpsView, event.getRawX(),
+ event.getRawY(), fromUdfpsView);
+ }
+ } else {
+ // Centroid with sensor sized view
+ withinSensorArea =
isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
+ }
+
if (withinSensorArea) {
Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
Log.v(TAG, "onTouch | action down");
@@ -525,9 +545,25 @@
? event.getPointerId(0)
: event.findPointerIndex(mActivePointerId);
if (idx == event.getActionIndex()) {
- boolean actionMoveWithinSensorArea =
- isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
- fromUdfpsView);
+ boolean actionMoveWithinSensorArea;
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ // Ellipse detection
+ actionMoveWithinSensorArea =
+ mUdfpsEllipseDetection.isGoodEllipseOverlap(event);
+ } else {
+ // Centroid with expanded overlay
+ actionMoveWithinSensorArea =
+ isWithinSensorArea(udfpsView, event.getRawX(idx),
+ event.getRawY(idx), fromUdfpsView);
+ }
+ } else {
+ // Centroid with sensor sized view
+ actionMoveWithinSensorArea =
+ isWithinSensorArea(udfpsView, event.getX(idx),
+ event.getY(idx), fromUdfpsView);
+ }
+
if ((fromUdfpsView || actionMoveWithinSensorArea)
&& shouldTryToDismissKeyguard()) {
Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE");
@@ -660,7 +696,7 @@
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
@NonNull @BiometricsBackground Executor biometricsExecutor,
- @NonNull BouncerInteractor bouncerInteractor) {
+ @NonNull PrimaryBouncerInteractor primaryBouncerInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -699,7 +735,7 @@
false /* resetLockoutRequiresHardwareAuthToken */);
mBiometricExecutor = biometricsExecutor;
- mBouncerInteractor = bouncerInteractor;
+ mPrimaryBouncerInteractor = primaryBouncerInteractor;
mDumpManager.registerDumpable(TAG, this);
@@ -725,6 +761,10 @@
udfpsHapticsSimulator.setUdfpsController(this);
udfpsShell.setUdfpsOverlayController(mUdfpsOverlayController);
+
+ if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ mUdfpsEllipseDetection = new UdfpsEllipseDetection(mOverlayParams);
+ }
}
/**
@@ -791,8 +831,8 @@
onFingerUp(mOverlay.getRequestId(), oldView);
}
final boolean removed = mOverlay.hide();
- if (mKeyguardViewManager.isShowingAlternateAuth()) {
- mKeyguardViewManager.resetAlternateAuth(true);
+ if (mKeyguardViewManager.isShowingAlternateBouncer()) {
+ mKeyguardViewManager.hideAlternateBouncer(true);
}
Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed);
} else {
@@ -832,7 +872,7 @@
Log.v(TAG, "aod lock icon long-press rejected by the falsing manager.");
return;
}
- mKeyguardViewManager.showBouncer(true);
+ mKeyguardViewManager.showPrimaryBouncer(true);
// play the same haptic as the LockIconViewController longpress
mVibrator.vibrate(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index d70861a..8db4927 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -49,7 +49,8 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -95,7 +96,7 @@
private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
private val activityLaunchAnimator: ActivityLaunchAnimator,
private val featureFlags: FeatureFlags,
- private val bouncerInteractor: BouncerInteractor,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
) {
/** The view, when [isShowing], or null. */
@@ -103,6 +104,7 @@
private set
private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
+ private var sensorBounds: Rect = Rect()
private var overlayTouchListener: TouchExplorationStateChangeListener? = null
@@ -120,6 +122,10 @@
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ }
}
/** A helper if the [requestReason] was due to enrollment. */
@@ -160,6 +166,7 @@
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
if (overlayView == null) {
overlayParams = params
+ sensorBounds = Rect(params.sensorBounds)
try {
overlayView = (inflater.inflate(
R.layout.udfps_view, null, false
@@ -178,6 +185,7 @@
}
windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+ sensorRect = sensorBounds
touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
overlayTouchListener = TouchExplorationStateChangeListener {
if (accessibilityManager.isTouchExplorationEnabled) {
@@ -194,6 +202,7 @@
overlayTouchListener!!
)
overlayTouchListener?.onTouchExplorationStateChanged(true)
+ useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
}
} catch (e: RuntimeException) {
Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
@@ -225,13 +234,14 @@
REASON_ENROLL_ENROLLING -> {
UdfpsEnrollViewController(
view.addUdfpsView(R.layout.udfps_enroll_view) {
- updateSensorLocation(overlayParams.sensorBounds)
+ updateSensorLocation(sensorBounds)
},
enrollHelper ?: throw IllegalStateException("no enrollment helper"),
statusBarStateController,
shadeExpansionStateManager,
dialogManager,
dumpManager,
+ featureFlags,
overlayParams.scaleFactor
)
}
@@ -252,7 +262,7 @@
controller,
activityLaunchAnimator,
featureFlags,
- bouncerInteractor
+ primaryBouncerInteractor
)
}
REASON_AUTH_BP -> {
@@ -420,7 +430,12 @@
}
// Original sensorBounds assume portrait mode.
- val rotatedSensorBounds = Rect(overlayParams.sensorBounds)
+ var rotatedBounds =
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ Rect(overlayParams.overlayBounds)
+ } else {
+ Rect(overlayParams.sensorBounds)
+ }
val rot = overlayParams.rotation
if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
@@ -434,18 +449,27 @@
} else {
Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot))
RotationUtils.rotateBounds(
- rotatedSensorBounds,
+ rotatedBounds,
overlayParams.naturalDisplayWidth,
overlayParams.naturalDisplayHeight,
rot
)
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ RotationUtils.rotateBounds(
+ sensorBounds,
+ overlayParams.naturalDisplayWidth,
+ overlayParams.naturalDisplayHeight,
+ rot
+ )
+ }
}
}
- x = rotatedSensorBounds.left - paddingX
- y = rotatedSensorBounds.top - paddingY
- height = rotatedSensorBounds.height() + 2 * paddingX
- width = rotatedSensorBounds.width() + 2 * paddingY
+ x = rotatedBounds.left - paddingX
+ y = rotatedBounds.top - paddingY
+ height = rotatedBounds.height() + 2 * paddingX
+ width = rotatedBounds.width() + 2 * paddingY
return this
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt
new file mode 100644
index 0000000..8ae4775
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.graphics.Point
+import android.graphics.Rect
+import android.util.RotationUtils
+import android.view.MotionEvent
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
+
+private const val TAG = "UdfpsEllipseDetection"
+
+private const val NEEDED_POINTS = 2
+
+class UdfpsEllipseDetection(overlayParams: UdfpsOverlayParams) {
+ var sensorRect = Rect()
+ var points: Array<Point> = emptyArray()
+
+ init {
+ sensorRect = Rect(overlayParams.sensorBounds)
+
+ points = calculateSensorPoints(sensorRect)
+ }
+
+ fun updateOverlayParams(params: UdfpsOverlayParams) {
+ sensorRect = Rect(params.sensorBounds)
+
+ val rot = params.rotation
+ RotationUtils.rotateBounds(
+ sensorRect,
+ params.naturalDisplayWidth,
+ params.naturalDisplayHeight,
+ rot
+ )
+
+ points = calculateSensorPoints(sensorRect)
+ }
+
+ fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
+ return points.count { checkPoint(event, it) } >= NEEDED_POINTS
+ }
+
+ private fun checkPoint(event: MotionEvent, point: Point): Boolean {
+ // Calculate if sensor point is within ellipse
+ // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
+ // yS))^2 / b^2) <= 1
+ val a: Float = cos(event.orientation) * (point.x - event.rawX)
+ val b: Float = sin(event.orientation) * (point.y - event.rawY)
+ val c: Float = sin(event.orientation) * (point.x - event.rawX)
+ val d: Float = cos(event.orientation) * (point.y - event.rawY)
+ val result =
+ (a + b).pow(2) / (event.touchMinor / 2).pow(2) +
+ (c - d).pow(2) / (event.touchMajor / 2).pow(2)
+
+ return result <= 1
+ }
+}
+
+fun calculateSensorPoints(sensorRect: Rect): Array<Point> {
+ val sensorX = sensorRect.centerX()
+ val sensorY = sensorRect.centerY()
+ val cornerOffset: Int = sensorRect.width() / 4
+ val sideOffset: Int = sensorRect.width() / 3
+
+ return arrayOf(
+ Point(sensorX - cornerOffset, sensorY - cornerOffset),
+ Point(sensorX, sensorY - sideOffset),
+ Point(sensorX + cornerOffset, sensorY - cornerOffset),
+ Point(sensorX - sideOffset, sensorY),
+ Point(sensorX, sensorY),
+ Point(sensorX + sideOffset, sensorY),
+ Point(sensorX - cornerOffset, sensorY + cornerOffset),
+ Point(sensorX, sensorY + sideOffset),
+ Point(sensorX + cornerOffset, sensorY + cornerOffset)
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 49e378e..af7e0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -99,12 +99,11 @@
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+ mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
if (!mIsAccessibilityEnabled) {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
- mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
} else {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
- mOnFirstBucketFailedColor = mHelpColor;
}
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
mCheckmarkDrawable.mutate();
@@ -197,6 +196,7 @@
}
}
+ mShowingHelp = showingHelp;
mRemainingSteps = remainingSteps;
mTotalSteps = totalSteps;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 69c37b2..87be42c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
@@ -41,6 +42,9 @@
@NonNull private ImageView mFingerprintView;
@NonNull private ImageView mFingerprintProgressView;
+ private LayoutParams mProgressParams;
+ private float mProgressBarRadius;
+
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
@@ -57,6 +61,32 @@
}
@Override
+ void onSensorRectUpdated(RectF bounds) {
+ if (mUseExpandedOverlay) {
+ RectF converted = getBoundsRelativeToView(bounds);
+
+ mProgressParams = new LayoutParams(
+ (int) (converted.width() + mProgressBarRadius * 2),
+ (int) (converted.height() + mProgressBarRadius * 2));
+ mProgressParams.setMargins(
+ (int) (converted.left - mProgressBarRadius),
+ (int) (converted.top - mProgressBarRadius),
+ (int) (converted.right + mProgressBarRadius),
+ (int) (converted.bottom + mProgressBarRadius)
+ );
+
+ mFingerprintProgressView.setLayoutParams(mProgressParams);
+ super.onSensorRectUpdated(converted);
+ } else {
+ super.onSensorRectUpdated(bounds);
+ }
+ }
+
+ void setProgressBarRadius(float radius) {
+ mProgressBarRadius = radius;
+ }
+
+ @Override
public UdfpsDrawable getDrawable() {
return mFingerprintDrawable;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index e01273f..4017665 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -21,6 +21,8 @@
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -57,6 +59,7 @@
@NonNull ShadeExpansionStateManager shadeExpansionStateManager,
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull DumpManager dumpManager,
+ @NonNull FeatureFlags featureFlags,
float scaleFactor) {
super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
dumpManager);
@@ -64,6 +67,11 @@
R.integer.config_udfpsEnrollProgressBar));
mEnrollHelper = enrollHelper;
mView.setEnrollHelper(mEnrollHelper);
+ mView.setProgressBarRadius(mEnrollProgressBarRadius);
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ mView.mUseExpandedOverlay = true;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index bc274a0..339b8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.View;
@@ -75,6 +76,8 @@
private int mAnimationType = ANIMATION_NONE;
private boolean mFullyInflated;
+ private LayoutParams mParams;
+
public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsFpDrawable(context);
@@ -239,6 +242,22 @@
updateAlpha();
}
+ @Override
+ void onSensorRectUpdated(RectF bounds) {
+ super.onSensorRectUpdated(bounds);
+
+ if (mUseExpandedOverlay) {
+ mParams = new LayoutParams((int) bounds.width(), (int) bounds.height());
+ RectF converted = getBoundsRelativeToView(bounds);
+ mParams.setMargins(
+ (int) converted.left,
+ (int) converted.top,
+ (int) converted.right,
+ (int) converted.bottom
+ );
+ }
+ }
+
/**
* Animates in the bg protection circle behind the fp icon to highlight the icon.
*/
@@ -277,6 +296,7 @@
pw.println(" mUdfpsRequested=" + mUdfpsRequested);
pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
pw.println(" mAnimationType=" + mAnimationType);
+ pw.println(" mUseExpandedOverlay=" + mUseExpandedOverlay);
}
private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
@@ -291,7 +311,12 @@
updatePadding();
updateColor();
updateAlpha();
- parent.addView(view);
+
+ if (mUseExpandedOverlay) {
+ parent.addView(view, mParams);
+ } else {
+ parent.addView(view);
+ }
// requires call to invalidate to update the color
mLockScreenFp.addValueCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 5bae2dc..63144fc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -31,7 +31,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionListener
@@ -40,9 +40,9 @@
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.KeyguardBouncer
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateBouncer
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
@@ -52,7 +52,6 @@
import java.io.PrintWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/** Class that coordinates non-HBM animations during keyguard authentication. */
@@ -73,7 +72,7 @@
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
featureFlags: FeatureFlags,
- private val bouncerInteractor: BouncerInteractor
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor
) :
UdfpsAnimationViewController<UdfpsKeyguardView>(
view,
@@ -82,6 +81,8 @@
systemUIDialogManager,
dumpManager
) {
+ private val useExpandedOverlay: Boolean =
+ featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
private var showingUdfpsBouncer = false
private var udfpsRequested = false
@@ -146,8 +147,8 @@
}
}
- private val bouncerExpansionCallback: BouncerExpansionCallback =
- object : BouncerExpansionCallback {
+ private val mPrimaryBouncerExpansionCallback: PrimaryBouncerExpansionCallback =
+ object : PrimaryBouncerExpansionCallback {
override fun onExpansionChanged(expansion: Float) {
inputBouncerHiddenAmount = expansion
updateAlpha()
@@ -180,7 +181,7 @@
private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
panelExpansionFraction =
- if (keyguardViewManager.isBouncerInTransit) {
+ if (keyguardViewManager.isPrimaryBouncerInTransit) {
aboutToShowBouncerProgress(fraction)
} else {
fraction
@@ -233,21 +234,27 @@
if (transitionToFullShadeProgress != 0f) {
return
}
- udfpsController.onTouch(event)
+
+ // Forwarding touches not needed with expanded overlay
+ if (useExpandedOverlay) {
+ return
+ } else {
+ udfpsController.onTouch(event)
+ }
}
}
- private val alternateAuthInterceptor: AlternateAuthInterceptor =
- object : AlternateAuthInterceptor {
- override fun showAlternateAuthBouncer(): Boolean {
+ private val mAlternateBouncer: AlternateBouncer =
+ object : AlternateBouncer {
+ override fun showAlternateBouncer(): Boolean {
return showUdfpsBouncer(true)
}
- override fun hideAlternateAuthBouncer(): Boolean {
+ override fun hideAlternateBouncer(): Boolean {
return showUdfpsBouncer(false)
}
- override fun isShowingAlternateAuthBouncer(): Boolean {
+ override fun isShowingAlternateBouncer(): Boolean {
return showingUdfpsBouncer
}
@@ -268,7 +275,7 @@
override fun onInit() {
super.onInit()
- keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
}
init {
@@ -285,7 +292,7 @@
@VisibleForTesting
internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
- bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+ primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
inputBouncerExpansion = bouncerExpansion
updateAlpha()
updatePauseAuth()
@@ -306,10 +313,10 @@
qsExpansion = keyguardViewManager.qsExpansion
keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
if (!isModernBouncerEnabled) {
- val bouncer = keyguardViewManager.bouncer
+ val bouncer = keyguardViewManager.primaryBouncer
bouncer?.expansion?.let {
- bouncerExpansionCallback.onExpansionChanged(it)
- bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+ mPrimaryBouncerExpansionCallback.onExpansionChanged(it)
+ bouncer.addBouncerExpansionCallback(mPrimaryBouncerExpansionCallback)
}
updateBouncerHiddenAmount()
}
@@ -319,9 +326,10 @@
view.updatePadding()
updateAlpha()
updatePauseAuth()
- keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
lockScreenShadeTransitionController.udfpsKeyguardViewController = this
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ view.mUseExpandedOverlay = useExpandedOverlay
}
override fun onViewDetached() {
@@ -329,7 +337,7 @@
faceDetectRunning = false
keyguardStateController.removeCallback(keyguardStateControllerCallback)
statusBarStateController.removeCallback(stateListener)
- keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+ keyguardViewManager.removeAlternateAuthInterceptor(mAlternateBouncer)
keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
configurationController.removeCallback(configurationListener)
shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
@@ -339,7 +347,9 @@
activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
if (!isModernBouncerEnabled) {
- keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+ keyguardViewManager.primaryBouncer?.removeBouncerExpansionCallback(
+ mPrimaryBouncerExpansionCallback
+ )
}
}
@@ -442,7 +452,7 @@
return if (isModernBouncerEnabled) {
inputBouncerExpansion == 1f
} else {
- keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+ keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateBouncer
}
}
@@ -462,7 +472,7 @@
*/
private fun maybeShowInputBouncer() {
if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
- keyguardViewManager.showBouncer(true)
+ keyguardViewManager.showPrimaryBouncer(true)
}
}
@@ -535,8 +545,8 @@
if (isModernBouncerEnabled) {
return
}
- val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
- if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+ val altBouncerShowing = keyguardViewManager.isShowingAlternateBouncer
+ if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
inputBouncerHiddenAmount = 1f
} else if (keyguardViewManager.isBouncerShowing) {
// input bouncer is fully showing
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index a15456d..4a8877e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -20,6 +20,7 @@
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
+import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
@@ -38,9 +39,12 @@
attrs: AttributeSet?
) : FrameLayout(context, attrs), DozeReceiver {
+ // Use expanded overlay when feature flag is true, set by UdfpsViewController
+ var useExpandedOverlay: Boolean = false
+
// sensorRect may be bigger than the sensor. True sensor dimensions are defined in
// overlayParams.sensorBounds
- private val sensorRect = RectF()
+ var sensorRect = Rect()
private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
private val debugTextPaint = Paint().apply {
isAntiAlias = true
@@ -92,13 +96,19 @@
val paddingX = animationViewController?.paddingX ?: 0
val paddingY = animationViewController?.paddingY ?: 0
- sensorRect.set(
- paddingX.toFloat(),
- paddingY.toFloat(),
- (overlayParams.sensorBounds.width() + paddingX).toFloat(),
- (overlayParams.sensorBounds.height() + paddingY).toFloat()
- )
- animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ // Updates sensor rect in relation to the overlay view
+ if (useExpandedOverlay) {
+ animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ } else {
+ sensorRect.set(
+ paddingX,
+ paddingY,
+ (overlayParams.sensorBounds.width() + paddingX),
+ (overlayParams.sensorBounds.height() + paddingY)
+ )
+
+ animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ }
}
fun onTouchOutsideView() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index c619648..6fb8e34 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -6,6 +6,8 @@
import android.view.inputmethod.InputMethodManager
import android.widget.ImeAwareEditText
import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
@@ -13,7 +15,7 @@
import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch
/** Sub-binder for the [CredentialPasswordView]. */
@@ -24,14 +26,19 @@
view: CredentialPasswordView,
host: CredentialView.Host,
viewModel: CredentialViewModel,
+ requestFocusForInput: Boolean,
) {
val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
+ val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
+
view.repeatWhenAttached {
- passwordField.requestFocus()
- passwordField.scheduleShowSoftInput()
+ if (requestFocusForInput) {
+ passwordField.requestFocus()
+ passwordField.scheduleShowSoftInput()
+ }
repeatOnLifecycle(Lifecycle.State.STARTED) {
// observe credential validation attempts and submit/cancel buttons
@@ -43,9 +50,7 @@
launch { viewModel.checkCredential(text, header) }
}
)
- passwordField.setOnKeyListener(
- OnBackButtonListener { host.onCredentialAborted() }
- )
+ passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
}
}
@@ -66,18 +71,35 @@
}
}
}
+
+ val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher()
+ if (onBackInvokedDispatcher != null) {
+ launch {
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ onBackInvokedCallback
+ )
+ awaitCancellation()
+ }
+ .invokeOnCompletion {
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+ onBackInvokedCallback
+ )
+ }
+ }
}
}
}
}
-private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) :
+ View.OnKeyListener {
override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
if (keyCode != KeyEvent.KEYCODE_BACK) {
return false
}
if (event.action == KeyEvent.ACTION_UP) {
- onBack()
+ onBackInvokedCallback.onBackInvoked()
}
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
index 4765551..b692ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -9,7 +9,6 @@
import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/** Sub-binder for the [CredentialPatternView]. */
@@ -30,7 +29,7 @@
viewModel.header.collect { header ->
lockPatternView.setOnPatternListener(
OnPatternDetectedListener { pattern ->
- if (pattern.isPatternLongEnough()) {
+ if (pattern.isPatternTooShort()) {
// Pattern size is less than the minimum
// do not count it as a failed attempt
viewModel.showPatternTooShortError()
@@ -71,5 +70,5 @@
}
}
-private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+private fun List<LockPatternView.Cell>.isPatternTooShort(): Boolean =
size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index fcc9487..e2d36dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -17,7 +17,6 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -40,6 +39,7 @@
panelViewController: AuthPanelController,
animatePanel: Boolean,
maxErrorDuration: Long = 3_000L,
+ requestFocusForInput: Boolean = true,
) {
val titleView: TextView = view.requireViewById(R.id.title)
val subtitleView: TextView = view.requireViewById(R.id.subtitle)
@@ -110,7 +110,8 @@
// bind the auth widget
when (view) {
- is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+ is CredentialPasswordView ->
+ CredentialPasswordViewBinder.bind(view, host, viewModel, requestFocusForInput)
is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 616e49c..1454210 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -31,7 +31,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index e82d0ea..3808ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -30,7 +30,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
/**
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 1455699..36103f8 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,9 +33,9 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader.RippleShape;
-import com.android.systemui.ripple.RippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleView;
import java.text.NumberFormat;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index beaccba..e8e1f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -231,7 +231,8 @@
// check for false tap if it is a seekbar interaction
if (interactionType == MEDIA_SEEKBAR) {
- localResult[0] &= isFalseTap(LOW_PENALTY);
+ localResult[0] &= isFalseTap(mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY : FalsingManager.LOW_PENALTY);
}
logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index c4723e8..5c905df 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -29,7 +29,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingPlugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.util.DeviceConfigProxy;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index c853671..fb37def 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -266,6 +266,7 @@
mExitAnimator.cancel();
}
reset();
+ mClipboardLogger.setClipSource(clipSource);
String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
@@ -525,21 +526,27 @@
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
+ private String mClipSource;
private boolean mGuarded = false;
ClipboardLogger(UiEventLogger uiEventLogger) {
mUiEventLogger = uiEventLogger;
}
+ void setClipSource(String clipSource) {
+ mClipSource = clipSource;
+ }
+
void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
if (!mGuarded) {
mGuarded = true;
- mUiEventLogger.log(event);
+ mUiEventLogger.log(event, 0, mClipSource);
}
}
void reset() {
mGuarded = false;
+ mClipSource = null;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index cece764..c194e66 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -20,7 +20,10 @@
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Build;
+import android.provider.DeviceConfig;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
import javax.inject.Inject;
@@ -35,6 +38,12 @@
if (clipData != null && clipData.getDescription().getExtras() != null
&& clipData.getDescription().getExtras().getBoolean(
ClipDescription.EXTRA_IS_REMOTE_DEVICE)) {
+ if (Build.isDebuggable() && DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE,
+ false)) {
+ return true;
+ }
ComponentName remoteComponent = ComponentName.unflattenFromString(
context.getResources().getString(R.string.config_remoteCopyPackage));
if (remoteComponent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 588ef5c..4dfcd63 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -16,16 +16,120 @@
package com.android.systemui.controls
+import android.Manifest
+import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.controls.ControlsProviderService
+import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo
+import java.util.Objects
class ControlsServiceInfo(
- context: Context,
+ private val context: Context,
val serviceInfo: ServiceInfo
) : DefaultAppInfo(
context,
context.packageManager,
context.userId,
serviceInfo.componentName
-)
\ No newline at end of file
+) {
+ private val _panelActivity: ComponentName?
+
+ init {
+ val metadata = serviceInfo.metaData
+ ?.getString(ControlsProviderService.META_DATA_PANEL_ACTIVITY) ?: ""
+ val unflatenned = ComponentName.unflattenFromString(metadata)
+ if (unflatenned != null && unflatenned.packageName == componentName.packageName) {
+ _panelActivity = unflatenned
+ } else {
+ _panelActivity = null
+ }
+ }
+
+ /**
+ * Component name of an activity that will be shown embedded in the device controls space
+ * instead of using the controls rendered by SystemUI.
+ *
+ * The activity must be in the same package, exported, enabled and protected by the
+ * [Manifest.permission.BIND_CONTROLS] permission.
+ */
+ var panelActivity: ComponentName? = null
+ private set
+
+ private var resolved: Boolean = false
+
+ @WorkerThread
+ fun resolvePanelActivity() {
+ if (resolved) return
+ resolved = true
+ panelActivity = _panelActivity?.let {
+ val resolveInfos = mPm.queryIntentActivitiesAsUser(
+ Intent().setComponent(it),
+ PackageManager.ResolveInfoFlags.of(
+ MATCH_DIRECT_BOOT_AWARE.toLong() or
+ MATCH_DIRECT_BOOT_UNAWARE.toLong()
+ ),
+ UserHandle.of(userId)
+ )
+ if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) {
+ it
+ } else {
+ null
+ }
+ }
+ }
+
+ /**
+ * Verifies that the panel activity is enabled, exported and protected by the correct
+ * permission. This last check is to prevent apps from forgetting to protect the activity, as
+ * they won't be able to see the panel until they do.
+ */
+ @WorkerThread
+ private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean {
+ return resolveInfo.activityInfo?.let {
+ it.permission == Manifest.permission.BIND_CONTROLS &&
+ it.exported && isComponentActuallyEnabled(it)
+ } ?: false
+ }
+
+ @WorkerThread
+ private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean {
+ return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled
+ else -> false
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is ControlsServiceInfo &&
+ userId == other.userId &&
+ componentName == other.componentName &&
+ panelActivity == other.panelActivity
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(userId, componentName, panelActivity)
+ }
+
+ fun copy(): ControlsServiceInfo {
+ return ControlsServiceInfo(context, serviceInfo).also {
+ it.panelActivity = this.panelActivity
+ }
+ }
+
+ override fun toString(): String {
+ return """
+ ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved)
+ """.trimIndent()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 5e8ce6d..7df0865 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -18,40 +18,47 @@
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Activity for rearranging and removing controls for a given structure
*/
-class ControlsEditingActivity @Inject constructor(
+open class ControlsEditingActivity @Inject constructor(
+ @Main private val mainExecutor: Executor,
private val controller: ControlsControllerImpl,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController
) : ComponentActivity() {
companion object {
+ private const val DEBUG = false
private const val TAG = "ControlsEditingActivity"
- private const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
+ const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
private val SUBTITLE_ID = R.string.controls_favorite_rearrange
private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
}
@@ -62,17 +69,24 @@
private lateinit var subtitle: TextView
private lateinit var saveButton: View
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
}
+ private val mOnBackInvokedCallback = OnBackInvokedCallback {
+ if (DEBUG) {
+ Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+ }
+ onBackPressed()
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -93,12 +107,23 @@
super.onStart()
setUpList()
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, mainExecutor)
+
+ if (DEBUG) {
+ Log.d(TAG, "Registered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
}
override fun onStop() {
super.onStop()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
+
+ if (DEBUG) {
+ Log.d(TAG, "Unregistered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
}
override fun onBackPressed() {
@@ -226,7 +251,7 @@
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
super.onDestroy()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index be572c5..3e97d31 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -20,10 +20,12 @@
import android.animation.AnimatorListenerAdapter
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.text.TextUtils
+import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
@@ -32,11 +34,12 @@
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.TooltipManager
import com.android.systemui.controls.controller.ControlsControllerImpl
@@ -44,21 +47,22 @@
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import java.text.Collator
import java.util.concurrent.Executor
import java.util.function.Consumer
import javax.inject.Inject
-class ControlsFavoritingActivity @Inject constructor(
+open class ControlsFavoritingActivity @Inject constructor(
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val uiController: ControlsUiController
) : ComponentActivity() {
companion object {
+ private const val DEBUG = false
private const val TAG = "ControlsFavoritingActivity"
// If provided and no structure is available, use as the title
@@ -67,7 +71,7 @@
// If provided, show this structure page first
const val EXTRA_STRUCTURE = "extra_structure"
const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure"
- internal const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
+ const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
private const val TOOLTIP_MAX_SHOWN = 2
}
@@ -91,17 +95,24 @@
private var cancelLoadRunnable: Runnable? = null
private var isPagerLoaded = false
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
}
+ private val mOnBackInvokedCallback = OnBackInvokedCallback {
+ if (DEBUG) {
+ Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+ }
+ onBackPressed()
+ }
+
private val listingCallback = object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
@@ -346,13 +357,19 @@
override fun onPause() {
super.onPause()
mTooltipManager?.hide(false)
- }
+ }
override fun onStart() {
super.onStart()
listingController.addCallback(listingCallback)
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, executor)
+
+ if (DEBUG) {
+ Log.d(TAG, "Registered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
}
override fun onResume() {
@@ -365,13 +382,19 @@
loadControls()
isPagerLoaded = true
}
- }
+ }
override fun onStop() {
super.onStop()
listingController.removeCallback(listingCallback)
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
+
+ if (DEBUG) {
+ Log.d(TAG, "Unregistered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+ mOnBackInvokedCallback)
}
override fun onConfigurationChanged(newConfig: Configuration) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 2d76ff2..c6428ef 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -18,17 +18,23 @@
import android.content.ComponentName
import android.content.Context
-import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.applications.ServiceListing
import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.Dumpable
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
@@ -57,16 +63,19 @@
private val context: Context,
@Background private val backgroundExecutor: Executor,
private val serviceListingBuilder: (Context) -> ServiceListing,
- userTracker: UserTracker
-) : ControlsListingController {
+ private val userTracker: UserTracker,
+ dumpManager: DumpManager,
+ featureFlags: FeatureFlags
+) : ControlsListingController, Dumpable {
@Inject
- constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
- context,
- executor,
- ::createServiceListing,
- userTracker
- )
+ constructor(
+ context: Context,
+ @Background executor: Executor,
+ userTracker: UserTracker,
+ dumpManager: DumpManager,
+ featureFlags: FeatureFlags
+ ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags)
private var serviceListing = serviceListingBuilder(context)
// All operations in background thread
@@ -76,27 +85,26 @@
private const val TAG = "ControlsListingControllerImpl"
}
- private var availableComponents = emptySet<ComponentName>()
- private var availableServices = emptyList<ServiceInfo>()
+ private var availableServices = emptyList<ControlsServiceInfo>()
private var userChangeInProgress = AtomicInteger(0)
override var currentUserId = userTracker.userId
private set
- private val serviceListingCallback = ServiceListing.Callback {
- val newServices = it.toList()
- val newComponents =
- newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
-
+ private val serviceListingCallback = ServiceListing.Callback { list ->
+ Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
+ val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
+ // After here, `list` is not captured, so we don't risk modifying it outside of the callback
backgroundExecutor.execute {
if (userChangeInProgress.get() > 0) return@execute
- if (!newComponents.equals(availableComponents)) {
- Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
- availableComponents = newComponents
+ if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
+ }
+
+ if (newServices != availableServices) {
availableServices = newServices
- val currentServices = getCurrentServices()
callbacks.forEach {
- it.onServicesUpdated(currentServices)
+ it.onServicesUpdated(getCurrentServices())
}
}
}
@@ -104,6 +112,7 @@
init {
Log.d(TAG, "Initializing")
+ dumpManager.registerDumpable(TAG, this)
serviceListing.addCallback(serviceListingCallback)
serviceListing.setListening(true)
serviceListing.reload()
@@ -165,7 +174,7 @@
* [ControlsProviderService]
*/
override fun getCurrentServices(): List<ControlsServiceInfo> =
- availableServices.map { ControlsServiceInfo(context, it) }
+ availableServices.map(ControlsServiceInfo::copy)
/**
* Get the localized label for the component.
@@ -174,7 +183,15 @@
* @return a label as returned by [CandidateInfo.loadLabel] or `null`.
*/
override fun getAppLabel(name: ComponentName): CharSequence? {
- return getCurrentServices().firstOrNull { it.componentName == name }
+ return availableServices.firstOrNull { it.componentName == name }
?.loadLabel()
}
+
+ override fun dump(writer: PrintWriter, args: Array<out String>) {
+ writer.println("ControlsListingController:")
+ writer.asIndenting().indentIfPossible {
+ println("Callbacks: $callbacks")
+ println("Services: ${getCurrentServices()}")
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index b26615f..90bc5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -18,58 +18,68 @@
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Activity to select an application to favorite the [Control] provided by them.
*/
-class ControlsProviderSelectorActivity @Inject constructor(
+open class ControlsProviderSelectorActivity @Inject constructor(
@Main private val executor: Executor,
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val uiController: ControlsUiController
) : ComponentActivity() {
companion object {
+ private const val DEBUG = false
private const val TAG = "ControlsProviderSelectorActivity"
const val BACK_SHOULD_EXIT = "back_should_exit"
}
private var backShouldExit = false
private lateinit var recyclerView: RecyclerView
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = listingController.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
}
+ private val mOnBackInvokedCallback = OnBackInvokedCallback {
+ if (DEBUG) {
+ Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+ }
+ onBackPressed()
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -119,7 +129,7 @@
override fun onStart() {
super.onStart()
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, executor)
recyclerView.alpha = 0.0f
recyclerView.adapter = AppAdapter(
@@ -141,11 +151,22 @@
}
})
}
+
+ if (DEBUG) {
+ Log.d(TAG, "Registered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
}
override fun onStop() {
super.onStop()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
+
+ if (DEBUG) {
+ Log.d(TAG, "Unregistered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
}
/**
@@ -169,7 +190,7 @@
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
index b376455..86bde5c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
@@ -19,6 +19,7 @@
import android.app.AlertDialog
import android.app.Dialog
import android.content.ComponentName
+import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
@@ -32,18 +33,20 @@
import android.widget.TextView
import androidx.activity.ComponentActivity
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.RenderInfo
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.concurrent.Executor
import javax.inject.Inject
open class ControlsRequestDialog @Inject constructor(
+ @Main private val mainExecutor: Executor,
private val controller: ControlsController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val controlsListingController: ControlsListingController
) : ComponentActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -58,12 +61,12 @@
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {}
}
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -72,7 +75,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, mainExecutor)
controlsListingController.addCallback(callback)
val requestUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL)
@@ -118,7 +121,7 @@
override fun onDestroy() {
dialog?.dismiss()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
controlsListingController.removeCallback(callback)
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 25418c3..0664e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.dagger;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
@@ -69,6 +70,7 @@
import android.os.ServiceManager;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.storage.StorageManager;
import android.permission.PermissionManager;
import android.safetycenter.SafetyCenterManager;
import android.service.dreams.DreamService;
@@ -110,6 +112,7 @@
/**
* Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
*/
+@SuppressLint("NonInjectedService")
@Module
public class FrameworkServicesModule {
@Provides
@@ -469,7 +472,13 @@
@Provides
@Singleton
- static SubscriptionManager provideSubcriptionManager(Context context) {
+ static StorageManager provideStorageManager(Context context) {
+ return context.getSystemService(StorageManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static SubscriptionManager provideSubscriptionManager(Context context) {
return context.getSystemService(SubscriptionManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index fe89c9a..9e8c0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -21,24 +21,21 @@
import com.android.systemui.dagger.qualifiers.InstrumentationTest;
import com.android.systemui.util.InitializationChecker;
-import javax.inject.Singleton;
-
import dagger.BindsInstance;
-import dagger.Component;
/**
* Base root component for Dagger injection.
*
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
* See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP.
*/
-@Singleton
-@Component(modules = {GlobalModule.class})
public interface GlobalRootComponent {
/**
* Builder for a GlobalRootComponent.
*/
- @Component.Builder
interface Builder {
@BindsInstance
Builder context(Context context);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index 7ab36e8..b30e0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,6 +16,8 @@
package com.android.systemui.dagger;
+import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import dagger.Subcomponent;
@@ -27,6 +29,7 @@
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
@@ -42,4 +45,9 @@
interface Builder extends SysUIComponent.Builder {
ReferenceSysUIComponent build();
}
+
+ /**
+ * Member injection into the supplied argument.
+ */
+ void inject(KeyguardQuickAffordanceProvider keyguardQuickAffordanceProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 2bee75e..d0c5007 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -45,6 +45,7 @@
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -164,7 +165,8 @@
ConfigurationController configurationController,
@Main Handler handler,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ ShadeExpansionStateManager shadeExpansionStateManager) {
return new HeadsUpManagerPhone(
context,
headsUpManagerLogger,
@@ -175,7 +177,8 @@
configurationController,
handler,
accessibilityManagerWrapper,
- uiEventLogger
+ uiEventLogger,
+ shadeExpansionStateManager
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index a14b0ee..6dc4f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,7 @@
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
@@ -65,6 +66,7 @@
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index d537d4b..000bbe6 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -54,6 +54,9 @@
private val receiverMap: Map<String, MutableList<DemoMode>>
init {
+ // Don't persist demo mode across restarts.
+ requestFinishDemoMode()
+
val m = mutableMapOf<String, MutableList<DemoMode>>()
DemoMode.COMMANDS.map { command ->
m.put(command, mutableListOf())
@@ -74,7 +77,6 @@
// content changes to know if the setting turned on or off
tracker.startTracking()
- // TODO: We should probably exit demo mode if we booted up with it on
isInDemoMode = tracker.isInDemoMode
val demoFilter = IntentFilter()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index b69afeb..0c14ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -133,9 +133,9 @@
/**
* Appends fling event to the logs
*/
- public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
+ public void traceFling(boolean expand, boolean aboveThreshold,
boolean screenOnFromTouch) {
- mLogger.logFling(expand, aboveThreshold, thresholdNeeded, screenOnFromTouch);
+ mLogger.logFling(expand, aboveThreshold, screenOnFromTouch);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 18c8e01..b5dbe21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -96,13 +96,11 @@
fun logFling(
expand: Boolean,
aboveThreshold: Boolean,
- thresholdNeeded: Boolean,
screenOnFromTouch: Boolean
) {
buffer.log(TAG, DEBUG, {
bool1 = expand
bool2 = aboveThreshold
- bool3 = thresholdNeeded
bool4 = screenOnFromTouch
}, {
"Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index d0258d3..f64d918 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -24,7 +24,6 @@
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
import android.annotation.AnyThread;
-import android.app.ActivityManager;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -50,6 +49,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +99,7 @@
private final SecureSettings mSecureSettings;
private final DevicePostureController mDevicePostureController;
private final AuthController mAuthController;
+ private final UserTracker mUserTracker;
private final boolean mScreenOffUdfpsEnabled;
// Sensors
@@ -152,7 +153,8 @@
ProximitySensor proximitySensor,
SecureSettings secureSettings,
AuthController authController,
- DevicePostureController devicePostureController
+ DevicePostureController devicePostureController,
+ UserTracker userTracker
) {
mSensorManager = sensorManager;
mConfig = config;
@@ -170,6 +172,7 @@
mDevicePostureController = devicePostureController;
mDevicePosture = mDevicePostureController.getDevicePosture();
mAuthController = authController;
+ mUserTracker = userTracker;
mUdfpsEnrolled =
mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
@@ -441,7 +444,7 @@
private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
- if (userId != ActivityManager.getCurrentUser()) {
+ if (userId != mUserTracker.getUserId()) {
return;
}
for (TriggerSensor s : mTriggerSensors) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index e8d7e46..f8bd1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -27,7 +27,7 @@
import com.android.systemui.plugins.DozeServicePlugin;
import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
new file mode 100644
index 0000000..12ceedd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.doze
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Receives doze transition events, and passes those events to registered callbacks. */
+@SysUISingleton
+class DozeTransitionListener @Inject constructor() :
+ DozeMachine.Part, CallbackController<DozeTransitionCallback> {
+ val callbacks = mutableSetOf<DozeTransitionCallback>()
+ var oldState = DozeMachine.State.UNINITIALIZED
+ var newState = DozeMachine.State.UNINITIALIZED
+
+ override fun transitionTo(oldState: DozeMachine.State, newState: DozeMachine.State) {
+ this.oldState = oldState
+ this.newState = newState
+ callbacks.forEach { it.onDozeTransition(oldState, newState) }
+ }
+
+ override fun addCallback(callback: DozeTransitionCallback) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: DozeTransitionCallback) {
+ callbacks.remove(callback)
+ }
+}
+
+interface DozeTransitionCallback {
+ fun onDozeTransition(oldState: DozeMachine.State, newState: DozeMachine.State)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 32cb1c0..0b69b80 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -45,6 +45,7 @@
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -188,7 +189,8 @@
UiEventLogger uiEventLogger,
SessionTracker sessionTracker,
KeyguardStateController keyguardStateController,
- DevicePostureController devicePostureController) {
+ DevicePostureController devicePostureController,
+ UserTracker userTracker) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -200,7 +202,7 @@
mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
- secureSettings, authController, devicePostureController);
+ secureSettings, authController, devicePostureController, userTracker);
mDockManager = dockManager;
mProxCheck = proxCheck;
mDozeLog = dozeLog;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 98cd2d7..069344f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -35,6 +35,7 @@
import com.android.systemui.doze.DozeSensors;
import com.android.systemui.doze.DozeSuppressor;
import com.android.systemui.doze.DozeSuspendScreenStatePreventingAdapter;
+import com.android.systemui.doze.DozeTransitionListener;
import com.android.systemui.doze.DozeTriggers;
import com.android.systemui.doze.DozeUi;
import com.android.systemui.doze.DozeWallpaperState;
@@ -83,7 +84,7 @@
DozeUi dozeUi, DozeScreenState dozeScreenState,
DozeScreenBrightness dozeScreenBrightness, DozeWallpaperState dozeWallpaperState,
DozeDockHandler dozeDockHandler, DozeAuthRemover dozeAuthRemover,
- DozeSuppressor dozeSuppressor) {
+ DozeSuppressor dozeSuppressor, DozeTransitionListener dozeTransitionListener) {
return new DozeMachine.Part[]{
dozePauser,
dozeFalsingManagerAdapter,
@@ -94,7 +95,8 @@
dozeWallpaperState,
dozeDockHandler,
dozeAuthRemover,
- dozeSuppressor
+ dozeSuppressor,
+ dozeTransitionListener
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index d8dd6a2..0087c84 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -17,17 +17,19 @@
package com.android.systemui.dreams
import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.view.View
+import android.view.animation.Interpolator
+import androidx.annotation.FloatRange
import androidx.core.animation.doOnEnd
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.statusbar.BlurUtils
-import java.util.function.Consumer
+import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
@@ -40,108 +42,239 @@
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
- private val mDreamInBlurAnimDuration: Int,
- @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+ private val mDreamInBlurAnimDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
+ private val mDreamInBlurAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
- private val mDreamInComplicationsAnimDuration: Int,
+ private val mDreamInComplicationsAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInTopComplicationsAnimDelay: Int,
+ private val mDreamInTopComplicationsAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInBottomComplicationsAnimDelay: Int
+ private val mDreamInBottomComplicationsAnimDelayMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
+ private val mDreamOutTranslationYDistance: Int,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
+ private val mDreamOutTranslationYDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+ private val mDreamOutTranslationYDelayBottomMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+ private val mDreamOutTranslationYDelayTopMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
+ private val mDreamOutAlphaDelayBottomMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
) {
- var mEntryAnimations: AnimatorSet? = null
+ private var mAnimator: Animator? = null
+
+ /**
+ * Store the current alphas at the various positions. This is so that we may resume an animation
+ * at the current alpha.
+ */
+ private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
+
+ @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
/** Starts the dream content and dream overlay entry animations. */
- fun startEntryAnimations(view: View) {
- cancelRunningEntryAnimations()
+ @JvmOverloads
+ fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ cancelAnimations()
- mEntryAnimations = AnimatorSet()
- mEntryAnimations?.apply {
- playTogether(
- buildDreamInBlurAnimator(view),
- buildDreamInTopComplicationsAnimator(),
- buildDreamInBottomComplicationsAnimator()
- )
- doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
- start()
- }
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ blurAnimator(
+ view = view,
+ from = 1f,
+ to = 0f,
+ durationMs = mDreamInBlurAnimDurationMs,
+ delayMs = mDreamInBlurAnimDelayMs
+ ),
+ alphaAnimator(
+ from = 0f,
+ to = 1f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = mDreamInTopComplicationsAnimDelayMs,
+ position = ComplicationLayoutParams.POSITION_TOP
+ ),
+ alphaAnimator(
+ from = 0f,
+ to = 1f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = mDreamInBottomComplicationsAnimDelayMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setEntryAnimationsFinished(true)
+ }
+ start()
+ }
+ }
+
+ /** Starts the dream content and dream overlay exit animations. */
+ @JvmOverloads
+ fun startExitAnimations(
+ view: View,
+ doneCallback: () -> Unit,
+ animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+ ) {
+ cancelAnimations()
+
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ blurAnimator(
+ view = view,
+ // Start the blurring wherever the entry animation ended, in
+ // case it was cancelled early.
+ from = mBlurProgress,
+ to = 1f,
+ durationMs = mDreamOutBlurDurationMs
+ ),
+ translationYAnimator(
+ from = 0f,
+ to = mDreamOutTranslationYDistance.toFloat(),
+ durationMs = mDreamOutTranslationYDurationMs,
+ delayMs = mDreamOutTranslationYDelayBottomMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM,
+ animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ ),
+ translationYAnimator(
+ from = 0f,
+ to = mDreamOutTranslationYDistance.toFloat(),
+ durationMs = mDreamOutTranslationYDurationMs,
+ delayMs = mDreamOutTranslationYDelayTopMs,
+ position = ComplicationLayoutParams.POSITION_TOP,
+ animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = ComplicationLayoutParams.POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamOutAlphaDurationMs,
+ delayMs = mDreamOutAlphaDelayBottomMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = ComplicationLayoutParams.POSITION_TOP,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamOutAlphaDurationMs,
+ delayMs = mDreamOutAlphaDelayTopMs,
+ position = ComplicationLayoutParams.POSITION_TOP
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setExitAnimationsRunning(false)
+ doneCallback()
+ }
+ start()
+ }
+ mOverlayStateController.setExitAnimationsRunning(true)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
- fun cancelRunningEntryAnimations() {
- if (mEntryAnimations?.isRunning == true) {
- mEntryAnimations?.cancel()
- }
- mEntryAnimations = null
+ fun cancelAnimations() {
+ mAnimator =
+ mAnimator?.let {
+ it.cancel()
+ null
+ }
}
- private fun buildDreamInBlurAnimator(view: View): Animator {
- return ValueAnimator.ofFloat(1f, 0f).apply {
- duration = mDreamInBlurAnimDuration.toLong()
- startDelay = mDreamInBlurAnimDelay.toLong()
+ private fun blurAnimator(
+ view: View,
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long = 0
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { animator: ValueAnimator ->
+ mBlurProgress = animator.animatedValue as Float
mBlurUtils.applyBlur(
- view.viewRootImpl,
- mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
- false /*opaque*/
+ viewRootImpl = view.viewRootImpl,
+ radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
+ opaque = false
)
}
}
}
- private fun buildDreamInTopComplicationsAnimator(): Animator {
- return ValueAnimator.ofFloat(0f, 1f).apply {
- duration = mDreamInComplicationsAnimDuration.toLong()
- startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+ private fun alphaAnimator(
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long,
+ @Position position: Int
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { va: ValueAnimator ->
- setTopElementsAlpha(va.animatedValue as Float)
+ setElementsAlphaAtPosition(
+ alpha = va.animatedValue as Float,
+ position = position,
+ fadingOut = to < from
+ )
}
}
}
- private fun buildDreamInBottomComplicationsAnimator(): Animator {
- return ValueAnimator.ofFloat(0f, 1f).apply {
- duration = mDreamInComplicationsAnimDuration.toLong()
- startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
- interpolator = Interpolators.LINEAR
+ private fun translationYAnimator(
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long,
+ @Position position: Int,
+ animInterpolator: Interpolator
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
+ interpolator = animInterpolator
addUpdateListener { va: ValueAnimator ->
- setBottomElementsAlpha(va.animatedValue as Float)
+ setElementsTranslationYAtPosition(va.animatedValue as Float, position)
}
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
- .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
- }
- }
- )
}
}
- /** Sets alpha of top complications and the status bar. */
- private fun setTopElementsAlpha(alpha: Float) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
- .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
- mStatusBarViewController.setAlpha(alpha)
- }
-
- /** Sets alpha of bottom complications. */
- private fun setBottomElementsAlpha(alpha: Float) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
- .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
- }
-
- private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
- if (alpha > 0 && view.visibility != View.VISIBLE) {
- view.visibility = View.VISIBLE
+ /** Sets alpha of complications at the specified position. */
+ private fun setElementsAlphaAtPosition(alpha: Float, position: Int, fadingOut: Boolean) {
+ mCurrentAlphaAtPosition[position] = alpha
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { view ->
+ if (fadingOut) {
+ CrossFadeHelper.fadeOut(view, 1 - alpha, /* remap= */ false)
+ } else {
+ CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
+ }
}
+ if (position == ComplicationLayoutParams.POSITION_TOP) {
+ mStatusBarViewController.setFadeAmount(alpha, fadingOut)
+ }
+ }
- view.alpha = alpha
+ /** Sets y translation of complications at the specified position. */
+ private fun setElementsTranslationYAtPosition(translationY: Float, position: Int) {
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
+ v.translationY = translationY
+ }
+ if (position == ComplicationLayoutParams.POSITION_TOP) {
+ mStatusBarViewController.setTranslationY(translationY)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index d0d0184..9d7ad30 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -29,19 +29,22 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController;
import java.util.Arrays;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -76,14 +79,14 @@
// Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
private final Handler mHandler;
private final int mDreamOverlayMaxTranslationY;
- private final BouncerCallbackInteractor mBouncerCallbackInteractor;
+ private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
private long mJitterStartTimeMillis;
private boolean mBouncerAnimating;
- private final KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback =
- new KeyguardBouncer.BouncerExpansionCallback() {
+ private final KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback =
+ new KeyguardBouncer.PrimaryBouncerExpansionCallback() {
@Override
public void onStartingToShow() {
@@ -136,7 +139,7 @@
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval,
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
- BouncerCallbackInteractor bouncerCallbackInteractor,
+ PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
DreamOverlayAnimationsController animationsController,
DreamOverlayStateController stateController) {
super(containerView);
@@ -160,7 +163,7 @@
mMaxBurnInOffset = maxBurnInOffset;
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
mMillisUntilFullJitter = millisUntilFullJitter;
- mBouncerCallbackInteractor = bouncerCallbackInteractor;
+ mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
}
@Override
@@ -173,11 +176,11 @@
protected void onViewAttached() {
mJitterStartTimeMillis = System.currentTimeMillis();
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
- final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
+ final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
if (bouncer != null) {
bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
- mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+ mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
@@ -188,13 +191,13 @@
@Override
protected void onViewDetached() {
mHandler.removeCallbacks(this::updateBurnInOffsets);
- final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
+ final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
if (bouncer != null) {
bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
- mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+ mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
- mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
+ mDreamOverlayAnimationsController.cancelAnimations();
}
View getContainerView() {
@@ -251,4 +254,17 @@
: aboutToShowBouncerProgress(expansion + 0.03f));
return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
}
+
+ /**
+ * Handle the dream waking up and run any necessary animations.
+ *
+ * @param onAnimationEnd Callback to trigger once animations are finished.
+ * @param callbackExecutor Executor to execute the callback on.
+ */
+ public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
+ mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
+ callbackExecutor.execute(onAnimationEnd);
+ return null;
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8542412..e76d5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -213,6 +213,15 @@
mLifecycleRegistry.setCurrentState(state);
}
+ @Override
+ public void onWakeUp(@NonNull Runnable onCompletedCallback) {
+ mExecutor.execute(() -> {
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+ }
+ });
+ }
+
/**
* Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
* called from the main executing thread. The window attributes closely mirror those that are
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e80d0be..5f942b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -52,6 +52,7 @@
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
+ public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -211,6 +212,14 @@
return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
}
+ /**
+ * Returns whether the dream content and dream overlay exit animations are running.
+ * @return {@code true} if animations are running, {@code false} otherwise.
+ */
+ public boolean areExitAnimationsRunning() {
+ return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -257,6 +266,15 @@
}
/**
+ * Sets whether dream content and dream overlay exit animations are running.
+ * @param running {@code true} if exit animations are running, {@code false} otherwise.
+ */
+ public void setExitAnimationsRunning(boolean running) {
+ modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE,
+ STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index d17fbe3..f1bb156 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -37,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -217,18 +218,29 @@
}
/**
- * Sets alpha of the dream overlay status bar.
+ * Sets fade of the dream overlay status bar.
*
* No-op if the dream overlay status bar should not be shown.
*/
- protected void setAlpha(float alpha) {
+ protected void setFadeAmount(float fadeAmount, boolean fadingOut) {
updateVisibility();
if (mView.getVisibility() != View.VISIBLE) {
return;
}
- mView.setAlpha(alpha);
+ if (fadingOut) {
+ CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false);
+ } else {
+ CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false);
+ }
+ }
+
+ /**
+ * Sets the y translation of the dream overlay status bar.
+ */
+ public void setTranslationY(float translationY) {
+ mView.setTranslationY(translationY);
}
private boolean shouldShowStatusBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 41f5578..b07efdf 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -197,11 +197,11 @@
*/
interface VisibilityController {
/**
- * Called to set the visibility of all shown and future complications.
+ * Called to set the visibility of all shown and future complications. Changes in visibility
+ * will always be animated.
* @param visibility The desired future visibility.
- * @param animate whether the change should be animated.
*/
- void setVisibility(@View.Visibility int visibility, boolean animate);
+ void setVisibility(@View.Visibility int visibility);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 5694f6d..48159ae 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -21,12 +21,9 @@
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Constraints;
@@ -34,6 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.touch.TouchInsetManager;
import java.util.ArrayList;
@@ -194,7 +192,9 @@
break;
}
- if (!isRoot) {
+ // Add margin if specified by the complication. Otherwise add default margin
+ // between complications.
+ if (mLayoutParams.isMarginSpecified() || !isRoot) {
final int margin = mLayoutParams.getMargin(mDefaultMargin);
switch(direction) {
case ComplicationLayoutParams.DIRECTION_DOWN:
@@ -479,7 +479,6 @@
private final TouchInsetManager.TouchInsetSession mSession;
private final int mFadeInDuration;
private final int mFadeOutDuration;
- private ViewPropertyAnimator mViewPropertyAnimator;
/** */
@Inject
@@ -496,26 +495,16 @@
}
@Override
- public void setVisibility(int visibility, boolean animate) {
- final boolean appearing = visibility == View.VISIBLE;
-
- if (mViewPropertyAnimator != null) {
- mViewPropertyAnimator.cancel();
+ public void setVisibility(int visibility) {
+ if (visibility == View.VISIBLE) {
+ CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0);
+ } else {
+ CrossFadeHelper.fadeOut(
+ mLayout,
+ mFadeOutDuration,
+ /* delay= */ 0,
+ /* endRunnable= */ null);
}
-
- if (appearing) {
- mLayout.setVisibility(View.VISIBLE);
- }
-
- mViewPropertyAnimator = mLayout.animate()
- .alpha(appearing ? 1f : 0f)
- .setDuration(appearing ? mFadeInDuration : mFadeOutDuration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLayout.setVisibility(visibility);
- }
- });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index a21eb19..4fae68d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -38,7 +38,7 @@
POSITION_START,
})
- @interface Position {}
+ public @interface Position {}
/** Align view with the top of parent or bottom of preceding {@link Complication}. */
public static final int POSITION_TOP = 1 << 0;
/** Align view with the bottom of parent or top of preceding {@link Complication}. */
@@ -261,6 +261,13 @@
}
/**
+ * Returns whether margin has been specified by the complication.
+ */
+ public boolean isMarginSpecified() {
+ return mMargin != MARGIN_UNSPECIFIED;
+ }
+
+ /**
* Returns the margin to apply between complications, or the given default if no margin is
* specified.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index cedd850a..c01cf43 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -33,6 +33,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
@@ -42,6 +43,8 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.util.ViewController;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -76,16 +79,25 @@
private final DreamOverlayStateController mDreamOverlayStateController;
private final ControlsComponent mControlsComponent;
- private boolean mControlServicesAvailable = false;
+ private boolean mOverlayActive = false;
// Callback for when the home controls service availability changes.
private final ControlsListingController.ControlsListingCallback mControlsCallback =
- serviceInfos -> {
- boolean available = !serviceInfos.isEmpty();
+ services -> updateHomeControlsComplication();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateComplicationAvailability();
+ private final DreamOverlayStateController.Callback mOverlayStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) {
+ return;
+ }
+
+ mOverlayActive = !mOverlayActive;
+
+ if (mOverlayActive) {
+ updateHomeControlsComplication();
+ }
}
};
@@ -102,18 +114,29 @@
public void start() {
mControlsComponent.getControlsListingController().ifPresent(
c -> c.addCallback(mControlsCallback));
+ mDreamOverlayStateController.addCallback(mOverlayStateCallback);
}
- private void updateComplicationAvailability() {
+ private void updateHomeControlsComplication() {
+ mControlsComponent.getControlsListingController().ifPresent(c -> {
+ if (isHomeControlsAvailable(c.getCurrentServices())) {
+ mDreamOverlayStateController.addComplication(mComplication);
+ } else {
+ mDreamOverlayStateController.removeComplication(mComplication);
+ }
+ });
+ }
+
+ private boolean isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices) {
+ if (controlsServices.isEmpty()) {
+ return false;
+ }
+
final boolean hasFavorites = mControlsComponent.getControlsController()
.map(c -> !c.getFavorites().isEmpty())
.orElse(false);
- if (!hasFavorites || !mControlServicesAvailable
- || mControlsComponent.getVisibility() == UNAVAILABLE) {
- mDreamOverlayStateController.removeComplication(mComplication);
- } else {
- mDreamOverlayStateController.addComplication(mComplication);
- }
+ final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
+ return hasFavorites && visibility != UNAVAILABLE;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index c9fecc9..09cc7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -41,6 +41,7 @@
public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
+ public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay";
/**
* Generates a {@link ConstraintLayout}, which can host
@@ -75,6 +76,16 @@
}
/**
+ * Provides the delay to wait for before fading out complications.
+ */
+ @Provides
+ @Named(COMPLICATIONS_FADE_OUT_DELAY)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesComplicationsFadeOutDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.complicationFadeOutDelayMs);
+ }
+
+ /**
* Provides the fade in duration for complications.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 7d2ce51..a514c47 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -48,9 +48,9 @@
int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0;
- int DREAM_MEDIA_COMPLICATION_WEIGHT = -1;
- int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1;
- int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 0;
+ int DREAM_MEDIA_COMPLICATION_WEIGHT = 0;
+ int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 2;
+ int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 1;
/**
* Provides layout parameters for the clock time complication.
@@ -60,10 +60,11 @@
static ComplicationLayoutParams provideClockTimeLayoutParams() {
return new ComplicationLayoutParams(0,
ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
+ ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ ComplicationLayoutParams.DIRECTION_UP,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT,
+ 0 /*margin*/);
}
/**
@@ -77,8 +78,11 @@
res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+ ComplicationLayoutParams.DIRECTION_UP,
+ DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT,
+ // Add margin to the bottom of home controls to horizontally align with smartspace.
+ res.getDimensionPixelSize(
+ R.dimen.dream_overlay_complication_home_controls_padding));
}
/**
@@ -101,14 +105,13 @@
*/
@Provides
@Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideSmartspaceLayoutParams() {
+ static ComplicationLayoutParams provideSmartspaceLayoutParams(@Main Resources res) {
return new ComplicationLayoutParams(0,
ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
+ ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
+ ComplicationLayoutParams.DIRECTION_END,
DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
- 0,
- true /*snapToGuide*/);
+ res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index f9dca08..101f4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -44,7 +44,7 @@
DreamOverlayComponent.class,
})
public interface DreamModule {
- String DREAM_ONLY_ENABLED_FOR_SYSTEM_USER = "dream_only_enabled_for_system_user";
+ String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
String DREAM_SUPPORTED = "dream_supported";
@@ -70,10 +70,10 @@
/** */
@Provides
- @Named(DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
- static boolean providesDreamOnlyEnabledForSystemUser(@Main Resources resources) {
+ @Named(DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+ static boolean providesDreamOnlyEnabledForDockUser(@Main Resources resources) {
return resources.getBoolean(
- com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser);
+ com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index cb012fa..ed0e1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -55,6 +55,22 @@
"dream_in_top_complications_anim_delay";
public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
"dream_in_bottom_complications_anim_delay";
+ public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
+ "dream_out_complications_translation_y";
+ public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
+ "dream_out_complications_translation_y_duration";
+ public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
+ "dream_out_complications_translation_y_delay_bottom";
+ public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
+ "dream_out_complications_translation_y_delay_top";
+ public static final String DREAM_OUT_ALPHA_DURATION =
+ "dream_out_complications_alpha_duration";
+ public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
+ "dream_out_complications_alpha_delay_bottom";
+ public static final String DREAM_OUT_ALPHA_DELAY_TOP =
+ "dream_out_complications_alpha_delay_top";
+ public static final String DREAM_OUT_BLUR_DURATION =
+ "dream_out_blur_duration";
/** */
@Provides
@@ -127,8 +143,8 @@
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DURATION)
- static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+ static long providesDreamInBlurAnimationDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
}
/**
@@ -136,8 +152,8 @@
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DELAY)
- static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+ static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
}
/**
@@ -145,8 +161,8 @@
*/
@Provides
@Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
- static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+ static long providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
}
/**
@@ -154,8 +170,8 @@
*/
@Provides
@Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
}
/**
@@ -163,8 +179,69 @@
*/
@Provides
@Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(
+ R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ }
+
+ /**
+ * Provides the number of pixels to translate complications when waking up from dream.
+ */
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
+ return (long) resources.getInteger(
+ R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DELAY_TOP)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_BLUR_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutBlurDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 0dba4ff..92cdcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -116,7 +116,7 @@
if (mCapture) {
// Since the user is dragging the bouncer up, set scrimmed to false.
- mStatusBarKeyguardViewManager.showBouncer(false);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
index 3087cdf..e276e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
@@ -16,22 +16,26 @@
package com.android.systemui.dreams.touch;
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT;
-import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayDeque;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -49,33 +53,58 @@
private static final String TAG = "HideComplicationHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final Complication.VisibilityController mVisibilityController;
private final int mRestoreTimeout;
+ private final int mFadeOutDelay;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final Handler mHandler;
- private final Executor mExecutor;
+ private final DelayableExecutor mExecutor;
+ private final DreamOverlayStateController mOverlayStateController;
private final TouchInsetManager mTouchInsetManager;
+ private final Complication.VisibilityController mVisibilityController;
+ private boolean mHidden = false;
+ @Nullable
+ private Runnable mHiddenCallback;
+ private final ArrayDeque<Runnable> mCancelCallbacks = new ArrayDeque<>();
+
private final Runnable mRestoreComplications = new Runnable() {
@Override
public void run() {
- mVisibilityController.setVisibility(View.VISIBLE, true);
+ mVisibilityController.setVisibility(View.VISIBLE);
+ mHidden = false;
+ }
+ };
+
+ private final Runnable mHideComplications = new Runnable() {
+ @Override
+ public void run() {
+ if (mOverlayStateController.areExitAnimationsRunning()) {
+ // Avoid interfering with the exit animations.
+ return;
+ }
+ mVisibilityController.setVisibility(View.INVISIBLE);
+ mHidden = true;
+ if (mHiddenCallback != null) {
+ mHiddenCallback.run();
+ mHiddenCallback = null;
+ }
}
};
@Inject
HideComplicationTouchHandler(Complication.VisibilityController visibilityController,
@Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout,
+ @Named(COMPLICATIONS_FADE_OUT_DELAY) int fadeOutDelay,
TouchInsetManager touchInsetManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @Main Executor executor,
- @Main Handler handler) {
+ @Main DelayableExecutor executor,
+ DreamOverlayStateController overlayStateController) {
mVisibilityController = visibilityController;
mRestoreTimeout = restoreTimeout;
+ mFadeOutDelay = fadeOutDelay;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- mHandler = handler;
mTouchInsetManager = touchInsetManager;
mExecutor = executor;
+ mOverlayStateController = overlayStateController;
}
@Override
@@ -87,7 +116,8 @@
final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
// If other sessions are interested in this touch, do not fade out elements.
- if (session.getActiveSessionCount() > 1 || bouncerShowing) {
+ if (session.getActiveSessionCount() > 1 || bouncerShowing
+ || mOverlayStateController.areExitAnimationsRunning()) {
if (DEBUG) {
Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount()
+ ". Bouncer showing: " + bouncerShowing);
@@ -115,8 +145,11 @@
touchCheck.addListener(() -> {
try {
if (!touchCheck.get()) {
- mHandler.removeCallbacks(mRestoreComplications);
- mVisibilityController.setVisibility(View.INVISIBLE, true);
+ // Cancel all pending callbacks.
+ while (!mCancelCallbacks.isEmpty()) mCancelCallbacks.pop().run();
+ mCancelCallbacks.add(
+ mExecutor.executeDelayed(
+ mHideComplications, mFadeOutDelay));
} else {
// If a touch occurred inside the dream overlay touch insets, do not
// handle the touch.
@@ -130,7 +163,23 @@
|| motionEvent.getAction() == MotionEvent.ACTION_UP) {
// End session and initiate delayed reappearance of the complications.
session.pop();
- mHandler.postDelayed(mRestoreComplications, mRestoreTimeout);
+ runAfterHidden(() -> mCancelCallbacks.add(
+ mExecutor.executeDelayed(mRestoreComplications,
+ mRestoreTimeout)));
+ }
+ });
+ }
+
+ /**
+ * Triggers a runnable after complications have been hidden. Will override any previously set
+ * runnable currently waiting for hide to happen.
+ */
+ private void runAfterHidden(Runnable runnable) {
+ mExecutor.execute(() -> {
+ if (mHidden) {
+ runnable.run();
+ } else {
+ mHiddenCallback = runnable;
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index a49aaccf..95e7ad96 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -34,9 +34,6 @@
fun isEnabled(flag: ResourceBooleanFlag): Boolean
/** Returns a boolean value for the given flag. */
- fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean
-
- /** Returns a boolean value for the given flag. */
fun isEnabled(flag: SysPropBooleanFlag): Boolean
/** Returns a string value for the given flag. */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 910c87a..81df4ed 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -40,7 +40,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
@@ -77,7 +76,6 @@
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
- private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
@@ -100,7 +98,6 @@
SecureSettings secureSettings,
SystemPropertiesHelper systemProperties,
@Main Resources resources,
- DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
Restarter restarter) {
@@ -109,7 +106,6 @@
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
- mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
mRestarter = restarter;
@@ -141,7 +137,7 @@
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
mBooleanFlagCache.put(id,
- readFlagValue(id, flag.getDefault()));
+ readBooleanFlagInternal(flag, flag.getDefault()));
}
return mBooleanFlagCache.get(id);
@@ -152,19 +148,7 @@
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
mBooleanFlagCache.put(id,
- readFlagValue(id, mResources.getBoolean(flag.getResourceId())));
- }
-
- return mBooleanFlagCache.get(id);
- }
-
- @Override
- public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
- int id = flag.getId();
- if (!mBooleanFlagCache.containsKey(id)) {
- boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
- flag.getName(), flag.getDefault());
- mBooleanFlagCache.put(id, readFlagValue(id, deviceConfigValue));
+ readBooleanFlagInternal(flag, mResources.getBoolean(flag.getResourceId())));
}
return mBooleanFlagCache.get(id);
@@ -180,7 +164,7 @@
id,
mSystemProperties.getBoolean(
flag.getName(),
- readFlagValue(id, flag.getDefault())));
+ readBooleanFlagInternal(flag, flag.getDefault())));
}
return mBooleanFlagCache.get(id);
@@ -192,7 +176,7 @@
int id = flag.getId();
if (!mStringFlagCache.containsKey(id)) {
mStringFlagCache.put(id,
- readFlagValue(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
+ readFlagValueInternal(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
}
return mStringFlagCache.get(id);
@@ -204,20 +188,21 @@
int id = flag.getId();
if (!mStringFlagCache.containsKey(id)) {
mStringFlagCache.put(id,
- readFlagValue(id, mResources.getString(flag.getResourceId()),
+ readFlagValueInternal(id, mResources.getString(flag.getResourceId()),
StringFlagSerializer.INSTANCE));
}
return mStringFlagCache.get(id);
}
+
@NonNull
@Override
public int getInt(@NonNull IntFlag flag) {
int id = flag.getId();
if (!mIntFlagCache.containsKey(id)) {
mIntFlagCache.put(id,
- readFlagValue(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
+ readFlagValueInternal(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
}
return mIntFlagCache.get(id);
@@ -229,27 +214,31 @@
int id = flag.getId();
if (!mIntFlagCache.containsKey(id)) {
mIntFlagCache.put(id,
- readFlagValue(id, mResources.getInteger(flag.getResourceId()),
+ readFlagValueInternal(id, mResources.getInteger(flag.getResourceId()),
IntFlagSerializer.INSTANCE));
}
return mIntFlagCache.get(id);
}
- /** Specific override for Boolean flags that checks against the teamfood list. */
- private boolean readFlagValue(int id, boolean defaultValue) {
- Boolean result = readBooleanFlagOverride(id);
- boolean hasServerOverride = mServerFlagReader.hasOverride(id);
+ /** Specific override for Boolean flags that checks against the teamfood list.*/
+ private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
+ Boolean result = readBooleanFlagOverride(flag.getId());
+ boolean hasServerOverride = mServerFlagReader.hasOverride(
+ flag.getNamespace(), flag.getName());
// Only check for teamfood if the default is false
// and there is no server override.
- if (!hasServerOverride && !defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
- if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
- return isEnabled(Flags.TEAMFOOD);
- }
+ if (!hasServerOverride
+ && !defaultValue
+ && result == null
+ && flag.getId() != Flags.TEAMFOOD.getId()
+ && flag.getTeamfood()) {
+ return isEnabled(Flags.TEAMFOOD);
}
- return result == null ? mServerFlagReader.readServerOverride(id, defaultValue) : result;
+ return result == null ? mServerFlagReader.readServerOverride(
+ flag.getNamespace(), flag.getName(), defaultValue) : result;
}
private Boolean readBooleanFlagOverride(int id) {
@@ -257,7 +246,8 @@
}
@NonNull
- private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+ private <T> T readFlagValueInternal(
+ int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
requireNonNull(defaultValue, "defaultValue");
T result = readFlagValueInternal(id, serializer);
return result == null ? defaultValue : result;
@@ -347,7 +337,6 @@
Log.i(TAG, "Android Restart Suppressed");
return;
}
- Log.i(TAG, "Restarting Android");
mRestarter.restart();
}
@@ -356,8 +345,6 @@
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
- } else if (flag instanceof DeviceConfigBooleanFlag) {
- setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof SysPropBooleanFlag) {
// Store SysProp flags in SystemProperties where they can read by outside parties.
mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
@@ -475,9 +462,6 @@
} else if (f instanceof ResourceBooleanFlag) {
enabled = isEnabled((ResourceBooleanFlag) f);
overridden = readBooleanFlagOverride(f.getId()) != null;
- } else if (f instanceof DeviceConfigBooleanFlag) {
- enabled = isEnabled((DeviceConfigBooleanFlag) f);
- overridden = false;
} else if (f instanceof SysPropBooleanFlag) {
// TODO(b/223379190): Teamfood not supported for sysprop flags yet.
enabled = isEnabled((SysPropBooleanFlag) f);
@@ -490,9 +474,11 @@
}
if (enabled) {
- return new ReleasedFlag(f.getId(), teamfood, overridden);
+ return new ReleasedFlag(
+ f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
} else {
- return new UnreleasedFlag(f.getId(), teamfood, overridden);
+ return new UnreleasedFlag(
+ f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
new file mode 100644
index 0000000..3d9f627
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.flags
+
+import android.util.Log
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Restarts SystemUI when the screen is locked. */
+class FeatureFlagsDebugRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val systemExitRestarter: SystemExitRestarter,
+) : Restarter {
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
+ restartNow()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+ if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
+ restartNow()
+ } else {
+ wakefulnessLifecycle.addObserver(observer)
+ }
+ }
+
+ private fun restartNow() {
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 8996d52..3c83682 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -101,7 +101,7 @@
@Override
public boolean isEnabled(@NotNull ReleasedFlag flag) {
- return mServerFlagReader.readServerOverride(flag.getId(), true);
+ return mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true);
}
@Override
@@ -115,18 +115,6 @@
}
@Override
- public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
- int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
- if (cacheIndex < 0) {
- boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
- flag.getName(), flag.getDefault());
- return isEnabled(flag.getId(), deviceConfigValue);
- }
-
- return mBooleanCache.valueAt(cacheIndex);
- }
-
- @Override
public boolean isEnabled(SysPropBooleanFlag flag) {
int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
if (cacheIndex < 0) {
@@ -180,10 +168,10 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("can override: false");
- Map<Integer, Flag<?>> knownFlags = Flags.collectFlags();
- for (Map.Entry<Integer, Flag<?>> idToFlag : knownFlags.entrySet()) {
- int id = idToFlag.getKey();
- Flag<?> flag = idToFlag.getValue();
+ Map<String, Flag<?>> knownFlags = FlagsFactory.INSTANCE.getKnownFlags();
+ for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
+ Flag<?> flag = nameToFlag.getValue();
+ int id = flag.getId();
boolean def = false;
if (mBooleanCache.indexOfKey(flag.getId()) < 0) {
if (flag instanceof SysPropBooleanFlag) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
new file mode 100644
index 0000000..a3f0f66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** Restarts SystemUI when the device appears idle. */
+class FeatureFlagsReleaseRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val batteryController: BatteryController,
+ @Background private val bgExecutor: DelayableExecutor,
+ private val systemExitRestarter: SystemExitRestarter
+) : Restarter {
+ var shouldRestart = false
+ var pendingRestart: Runnable? = null
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ maybeScheduleRestart()
+ }
+ }
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ maybeScheduleRestart()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
+ if (!shouldRestart) {
+ // Don't bother scheduling twice.
+ shouldRestart = true
+ wakefulnessLifecycle.addObserver(observer)
+ batteryController.addCallback(batteryCallback)
+ maybeScheduleRestart()
+ }
+ }
+
+ private fun maybeScheduleRestart() {
+ if (
+ wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
+ ) {
+ if (pendingRestart == null) {
+ pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
+ }
+ } else if (pendingRestart != null) {
+ pendingRestart?.run()
+ pendingRestart = null
+ }
+ }
+
+ private fun restartNow() {
+ Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index ad4b87d..b7fc0e4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -229,7 +229,7 @@
}
private int flagNameToId(String flagName) {
- Map<String, Flag<?>> flagFields = Flags.getFlagFields();
+ Map<String, Flag<?>> flagFields = FlagsFactory.INSTANCE.getKnownFlags();
for (String fieldName : flagFields.keySet()) {
if (flagName.equals(fieldName)) {
return flagFields.get(fieldName).getId();
@@ -240,7 +240,7 @@
}
private void printKnownFlags(PrintWriter pw) {
- Map<String, Flag<?>> fields = Flags.getFlagFields();
+ Map<String, Flag<?>> fields = FlagsFactory.INSTANCE.getKnownFlags();
int longestFieldName = 0;
for (String fieldName : fields.keySet()) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 804eef3..5dd5839 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -17,12 +17,11 @@
import android.provider.DeviceConfig
import com.android.internal.annotations.Keep
-import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
-import kotlin.reflect.KClass
-import kotlin.reflect.full.declaredMembers
-import kotlin.reflect.full.isSubclassOf
-import kotlin.reflect.full.staticProperties
+import com.android.systemui.flags.FlagsFactory.releasedFlag
+import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag
+import com.android.systemui.flags.FlagsFactory.sysPropBooleanFlag
+import com.android.systemui.flags.FlagsFactory.unreleasedFlag
/**
* List of [Flag] objects for use in SystemUI.
@@ -37,375 +36,388 @@
* See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
*/
object Flags {
- @JvmField val TEAMFOOD = UnreleasedFlag(1)
+ @JvmField val TEAMFOOD = unreleasedFlag(1, "teamfood")
// 100 - notification
// TODO(b/254512751): Tracking Bug
- val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = UnreleasedFlag(103)
+ val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
+ unreleasedFlag(103, "notification_pipeline_developer_logging")
// TODO(b/254512732): Tracking Bug
- @JvmField val NSSL_DEBUG_LINES = UnreleasedFlag(105)
+ @JvmField val NSSL_DEBUG_LINES = unreleasedFlag(105, "nssl_debug_lines")
// TODO(b/254512505): Tracking Bug
- @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = UnreleasedFlag(106)
+ @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag(106, "nssl_debug_remove_animation")
// TODO(b/254512624): Tracking Bug
@JvmField
val NOTIFICATION_DRAG_TO_CONTENTS =
- ResourceBooleanFlag(108, R.bool.config_notificationToContents)
+ resourceBooleanFlag(
+ 108,
+ R.bool.config_notificationToContents,
+ "notification_drag_to_contents"
+ )
// TODO(b/254512517): Tracking Bug
- val FSI_REQUIRES_KEYGUARD = UnreleasedFlag(110, teamfood = true)
+ val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true)
+
+ // TODO(b/259130119): Tracking Bug
+ val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
// TODO(b/254512538): Tracking Bug
- val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
+ val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
// TODO(b/254512425): Tracking Bug
- val NOTIFICATION_MEMORY_MONITOR_ENABLED = ReleasedFlag(112)
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED =
+ releasedFlag(112, "notification_memory_monitor_enabled")
// TODO(b/254512731): Tracking Bug
- @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
- val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
- val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
+ @JvmField
+ val NOTIFICATION_DISMISSAL_FADE =
+ unreleasedFlag(113, "notification_dismissal_fade", teamfood = true)
- @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, teamfood = true)
- // next id: 117
+ // TODO(b/259558771): Tracking Bug
+ val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
+
+ // TODO(b/259559750): Tracking Bug
+ val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
+
+ @JvmField
+ val NOTIFICATION_GROUP_CORNER =
+ unreleasedFlag(116, "notification_group_corner", teamfood = true)
+
+ // TODO(b/259217907)
+ @JvmField
+ val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
+ unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+
+ // TODO(b/257506350): Tracking Bug
+ val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
+
+ @JvmField
+ val SIMPLIFIED_APPEAR_FRACTION =
+ unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)
+
+ // TODO(b/257315550): Tracking Bug
+ val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+
+ val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
+ unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
// new BooleanFlag(200, true);
// TODO(b/254512713): Tracking Bug
- @JvmField val LOCKSCREEN_ANIMATIONS = ReleasedFlag(201)
+ @JvmField val LOCKSCREEN_ANIMATIONS = releasedFlag(201, "lockscreen_animations")
// TODO(b/254512750): Tracking Bug
- val NEW_UNLOCK_SWIPE_ANIMATION = ReleasedFlag(202)
- val CHARGING_RIPPLE = ResourceBooleanFlag(203, R.bool.flag_charging_ripple)
+ val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag(202, "new_unlock_swipe_animation")
+ val CHARGING_RIPPLE = resourceBooleanFlag(203, R.bool.flag_charging_ripple, "charging_ripple")
// TODO(b/254512281): Tracking Bug
@JvmField
- val BOUNCER_USER_SWITCHER = ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher)
+ val BOUNCER_USER_SWITCHER =
+ resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
// TODO(b/254512676): Tracking Bug
- @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = UnreleasedFlag(207, teamfood = true)
+ @JvmField
+ val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true)
/**
* Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
* replacement of KeyguardBouncer.java.
*/
// TODO(b/254512385): Tracking Bug
- @JvmField val MODERN_BOUNCER = ReleasedFlag(208)
-
- /**
- * Whether the user interactor and repository should use `UserSwitcherController`.
- *
- * If this is `false`, the interactor and repo skip the controller and directly access the
- * framework APIs.
- */
- // TODO(b/254513286): Tracking Bug
- val USER_INTERACTOR_AND_REPO_USE_CONTROLLER = UnreleasedFlag(210)
-
- /**
- * Whether `UserSwitcherController` should use the user interactor.
- *
- * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
- * through the interactor.
- *
- * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
- * would created a cycle between controller -> interactor -> controller.
- */
- // TODO(b/254513102): Tracking Bug
- val USER_CONTROLLER_USES_INTERACTOR = ReleasedFlag(211)
+ @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
/**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
*/
- @JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+ @JvmField val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation")
/**
* Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
* will occur in stages. This is one stage of many to come.
*/
// TODO(b/255607168): Tracking Bug
- @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213)
+ @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
- @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214)
+ // TODO(b/252897742): Tracking Bug
+ @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection")
- @JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)
+ // TODO(b/252897742): Tracking Bug
+ @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay")
/**
* Whether to enable the code powering customizable lock screen quick affordances.
*
- * Note that this flag does not enable individual implementations of quick affordances like the
- * new camera quick affordance. Look for individual flags for those.
+ * This flag enables any new prebuilt quick affordances as well.
*/
- @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(216, teamfood = false)
+ // TODO(b/255618149): Tracking Bug
+ @JvmField
+ val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
+ unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
+
+ /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
+ // TODO(b/256513609): Tracking Bug
+ @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
- @JvmField val POWER_MENU_LITE = ReleasedFlag(300)
+ @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
// 400 - smartspace
// TODO(b/254513100): Tracking Bug
- val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = ReleasedFlag(401)
- val SMARTSPACE = ResourceBooleanFlag(402, R.bool.flag_smartspace)
+ val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
+ releasedFlag(401, "smartspace_shared_element_transition_enabled")
+ val SMARTSPACE = resourceBooleanFlag(402, R.bool.flag_smartspace, "smartspace")
// 500 - quick settings
// TODO(b/254512321): Tracking Bug
- @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
- val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
+ @JvmField val COMBINED_QS_HEADERS = releasedFlag(501, "combined_qs_headers")
+ val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
@JvmField
val QS_USER_DETAIL_SHORTCUT =
- ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut)
-
- // TODO(b/254512747): Tracking Bug
- val NEW_HEADER = UnreleasedFlag(505, teamfood = true)
+ resourceBooleanFlag(
+ 503,
+ R.bool.flag_lockscreen_qs_user_detail_shortcut,
+ "qs_user_detail_shortcut"
+ )
// TODO(b/254512383): Tracking Bug
@JvmField
val FULL_SCREEN_USER_SWITCHER =
- ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher)
+ resourceBooleanFlag(
+ 506,
+ R.bool.config_enableFullscreenUserSwitcher,
+ "full_screen_user_switcher"
+ )
// TODO(b/254512678): Tracking Bug
- @JvmField val NEW_FOOTER_ACTIONS = ReleasedFlag(507)
+ @JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
// TODO(b/244064524): Tracking Bug
- @JvmField val QS_SECONDARY_DATA_SUB_INFO = UnreleasedFlag(508, teamfood = true)
+ @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
// 600- status bar
// TODO(b/254513246): Tracking Bug
- val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
+ val STATUS_BAR_USER_SWITCHER =
+ resourceBooleanFlag(602, R.bool.flag_user_switcher_chip, "status_bar_user_switcher")
// TODO(b/254512623): Tracking Bug
@Deprecated("Replaced by mobile and wifi specific flags.")
- val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
+ val NEW_STATUS_BAR_PIPELINE_BACKEND =
+ unreleasedFlag(604, "new_status_bar_pipeline_backend", teamfood = false)
// TODO(b/254512660): Tracking Bug
@Deprecated("Replaced by mobile and wifi specific flags.")
- val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+ val NEW_STATUS_BAR_PIPELINE_FRONTEND =
+ unreleasedFlag(605, "new_status_bar_pipeline_frontend", teamfood = false)
// TODO(b/256614753): Tracking Bug
- val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606)
+ val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons")
// TODO(b/256614210): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607)
+ val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon")
// TODO(b/256614751): Tracking Bug
- val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = UnreleasedFlag(608)
+ val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
+ unreleasedFlag(608, "new_status_bar_mobile_icons_backend")
// TODO(b/256613548): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON_BACKEND = UnreleasedFlag(609)
+ val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
+
+ // TODO(b/256623670): Tracking Bug
+ @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
- val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
+ val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
// TODO(b/254512681): Tracking Bug
- val ONGOING_CALL_IN_IMMERSIVE = ReleasedFlag(701)
+ val ONGOING_CALL_IN_IMMERSIVE = releasedFlag(701, "ongoing_call_in_immersive")
// TODO(b/254512753): Tracking Bug
- val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = ReleasedFlag(702)
+ val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag(702, "ongoing_call_in_immersive_chip_tap")
// 800 - general visual/theme
- @JvmField val MONET = ResourceBooleanFlag(800, R.bool.flag_monet)
+ @JvmField val MONET = resourceBooleanFlag(800, R.bool.flag_monet, "monet")
// 801 - region sampling
// TODO(b/254512848): Tracking Bug
- val REGION_SAMPLING = UnreleasedFlag(801)
+ val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
// 802 - wallpaper rendering
// TODO(b/254512923): Tracking Bug
- @JvmField val USE_CANVAS_RENDERER = UnreleasedFlag(802, teamfood = true)
+ @JvmField val USE_CANVAS_RENDERER = unreleasedFlag(802, "use_canvas_renderer")
// 803 - screen contents translation
// TODO(b/254513187): Tracking Bug
- val SCREEN_CONTENTS_TRANSLATION = UnreleasedFlag(803)
+ val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
// 804 - monochromatic themes
- @JvmField val MONOCHROMATIC_THEMES = UnreleasedFlag(804)
+ @JvmField
+ val MONOCHROMATIC_THEMES =
+ sysPropBooleanFlag(804, "persist.sysui.monochromatic", default = false)
// 900 - media
// TODO(b/254512697): Tracking Bug
- val MEDIA_TAP_TO_TRANSFER = ReleasedFlag(900)
+ val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
// TODO(b/254512502): Tracking Bug
- val MEDIA_SESSION_ACTIONS = UnreleasedFlag(901)
+ val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
// TODO(b/254512726): Tracking Bug
- val MEDIA_NEARBY_DEVICES = ReleasedFlag(903)
+ val MEDIA_NEARBY_DEVICES = releasedFlag(903, "media_nearby_devices")
// TODO(b/254512695): Tracking Bug
- val MEDIA_MUTE_AWAIT = ReleasedFlag(904)
+ val MEDIA_MUTE_AWAIT = releasedFlag(904, "media_mute_await")
// TODO(b/254512654): Tracking Bug
- @JvmField val DREAM_MEDIA_COMPLICATION = UnreleasedFlag(905)
+ @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag(905, "dream_media_complication")
// TODO(b/254512673): Tracking Bug
- @JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906)
+ @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag(906, "dream_media_tap_to_open")
// TODO(b/254513168): Tracking Bug
- @JvmField val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
+ @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
+
+ @JvmField val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media")
// 1000 - dock
- val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
+ val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
// TODO(b/254512758): Tracking Bug
- @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
+ @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
// 1100 - windowing
@Keep
@JvmField
val WM_ENABLE_SHELL_TRANSITIONS =
- SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
+ sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = false)
// TODO(b/254513207): Tracking Bug
@Keep
@JvmField
val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- DeviceConfigBooleanFlag(
+ unreleasedFlag(
1102,
- "record_task_content",
- DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- false,
+ name = "record_task_content",
+ namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
teamfood = true
)
// TODO(b/254512674): Tracking Bug
@Keep
@JvmField
- val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+ val HIDE_NAVBAR_WINDOW =
+ sysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", default = false)
@Keep
@JvmField
- val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+ val WM_DESKTOP_WINDOWING =
+ sysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", default = false)
@Keep
@JvmField
- val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+ val WM_CAPTION_ON_SHELL =
+ sysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", default = false)
@Keep
@JvmField
val ENABLE_FLING_TO_DISMISS_BUBBLE =
- SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+ sysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", default = true)
@Keep
@JvmField
val ENABLE_FLING_TO_DISMISS_PIP =
- SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+ sysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", default = true)
@Keep
@JvmField
val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
+ sysPropBooleanFlag(
+ 1110,
+ "persist.wm.debug.enable_pip_keep_clear_algorithm",
+ default = false
+ )
// TODO(b/256873975): Tracking Bug
- @JvmField @Keep val WM_BUBBLE_BAR = UnreleasedFlag(1111)
+ @JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
// 1200 - predictive back
@Keep
@JvmField
val WM_ENABLE_PREDICTIVE_BACK =
- SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+ sysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", default = true)
@Keep
@JvmField
val WM_ENABLE_PREDICTIVE_BACK_ANIM =
- SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+ sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = false)
@Keep
@JvmField
val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
- SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
+ sysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", default = false)
// TODO(b/254512728): Tracking Bug
- @JvmField val NEW_BACK_AFFORDANCE = UnreleasedFlag(1203, teamfood = false)
+ @JvmField
+ val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false)
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
- @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, teamfood = true)
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
// TODO(b/254513155): Tracking Bug
- @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
+ @JvmField
+ val SCREENSHOT_WORK_PROFILE_POLICY =
+ unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
- val QUICK_TAP_IN_PCC = ReleasedFlag(1400)
+ val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
// 1500 - chooser
// TODO(b/254512507): Tracking Bug
- val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true)
+ val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
// 1600 - accessibility
- @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
+ @JvmField
+ val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
+ unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
// 1700 - clipboard
- @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, teamfood = true)
- @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
+ @JvmField
+ val CLIPBOARD_REMOTE_BEHAVIOR =
+ unreleasedFlag(1701, "clipboard_remote_behavior", teamfood = true)
// 1800 - shade container
- @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, teamfood = true)
+ @JvmField
+ val LEAVE_SHADE_OPEN_FOR_BUGREPORT =
+ unreleasedFlag(1800, "leave_shade_open_for_bugreport", teamfood = true)
// 1900 - note task
- @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+ @JvmField val NOTE_TASKS = sysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
// 2000 - device controls
- @Keep @JvmField val USE_APP_PANELS = UnreleasedFlag(2000, teamfood = true)
+ @Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true)
// 2100 - Falsing Manager
- @JvmField val FALSING_FOR_LONG_TAPS = ReleasedFlag(2100)
+ @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
- // Pay no attention to the reflection behind the curtain.
- // ========================== Curtain ==========================
- // | |
- // | . . . . . . . . . . . . . . . . . . . |
- @JvmStatic
- fun collectFlags(): Map<Int, Flag<*>> {
- return flagFields.mapKeys { field -> field.value.id }
- }
+ // 2200 - udfps
+ // TODO(b/259264861): Tracking Bug
+ @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
+ @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
+ @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
- // | . . . . . . . . . . . . . . . . . . . |
- @JvmStatic
- val flagFields: Map<String, Flag<*>>
- get() = collectFlagsInClass(Flags)
-
- @VisibleForTesting
- fun collectFlagsInClass(instance: Any): Map<String, Flag<*>> {
- val cls = instance::class
- val javaPropNames = cls.java.fields.map { it.name }
- val props = cls.declaredMembers
- val staticProps = cls.staticProperties
- val staticPropNames = staticProps.map { it.name }
- return props
- .mapNotNull { property ->
- if ((property.returnType.classifier as KClass<*>).isSubclassOf(Flag::class)) {
- // Fields with @JvmStatic should be accessed via java mechanisms
- if (javaPropNames.contains(property.name)) {
- property.name to cls.java.getField(property.name)[null] as Flag<*>
- // Fields with @Keep but not @JvmField. Don't do this.
- } else if (staticPropNames.contains(property.name)) {
- // The below code causes access violation exceptions. I don't know why.
- // property.name to (property.call() as Flag<*>)
- // property.name to (staticProps.find { it.name == property.name }!!
- // .getter.call() as Flag<*>)
- throw java.lang.RuntimeException(
- "The {$property.name} flag needs @JvmField"
- )
- // Everything else. Skip the `get` prefixed fields that kotlin adds.
- } else if (property.name.subSequence(0, 3) != "get") {
- property.name to (property.call(instance) as Flag<*>)
- } else {
- null
- }
- } else {
- null
- }
- }
- .toMap()
- }
- // | |
- // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+ // TODO(b259590361): Tracking bug
+ val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index e1f4944..8442230 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -30,17 +29,7 @@
@Provides
@Named(ALL_FLAGS)
fun providesAllFlags(): Map<Int, Flag<*>> {
- return Flags.collectFlags()
- }
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object : Restarter {
- override fun restart() {
- barService.restart()
- }
- }
+ return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index 694fa01..ae05c46 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -27,11 +27,10 @@
interface ServerFlagReader {
/** Returns true if there is a server-side setting stored. */
- fun hasOverride(flagId: Int): Boolean
+ fun hasOverride(namespace: String, name: String): Boolean
/** Returns any stored server-side setting or the default if not set. */
- fun readServerOverride(flagId: Int, default: Boolean): Boolean
-
+ fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean
/** Register a listener for changes to any of the passed in flags. */
fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
@@ -68,19 +67,19 @@
}
}
- override fun hasOverride(flagId: Int): Boolean =
- deviceConfig.getProperty(
+ override fun hasOverride(namespace: String, name: String): Boolean =
+ !namespace.isBlank() && !name.isBlank() && deviceConfig.getProperty(
namespace,
- getServerOverrideName(flagId)
+ name
) != null
- override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
- return deviceConfig.getBoolean(
+
+ override fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean =
+ !namespace.isBlank() && !name.isBlank() && deviceConfig.getBoolean(
namespace,
- getServerOverrideName(flagId),
+ name,
default
)
- }
override fun listenForChanges(
flags: Collection<Flag<*>>,
@@ -121,24 +120,24 @@
}
class ServerFlagReaderFake : ServerFlagReader {
- private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
+ private val flagMap: MutableMap<String, Boolean> = mutableMapOf()
private val listeners =
mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
- override fun hasOverride(flagId: Int): Boolean {
- return flagMap.containsKey(flagId)
+ override fun hasOverride(namespace: String, name: String): Boolean {
+ return flagMap.containsKey(name)
}
- override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
- return flagMap.getOrDefault(flagId, default)
+ override fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean {
+ return flagMap.getOrDefault(name, default)
}
- fun setFlagValue(flagId: Int, value: Boolean) {
- flagMap.put(flagId, value)
+ fun setFlagValue(namespace: String, name: String, value: Boolean) {
+ flagMap.put(name, value)
for ((listener, flags) in listeners) {
flagLoop@ for (flag in flags) {
- if (flagId == flag.id) {
+ if (name == flag.name) {
listener.onChange()
break@flagLoop
}
@@ -146,8 +145,8 @@
}
}
- fun eraseFlag(flagId: Int) {
- flagMap.remove(flagId)
+ fun eraseFlag(namespace: String, name: String) {
+ flagMap.remove(name)
}
override fun listenForChanges(
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
similarity index 76%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index 817c209f..f1b1be4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package com.android.systemui.flags
-import com.android.settingslib.spa.framework.EntryProvider
+import javax.inject.Inject
-class GalleryEntryProvider : EntryProvider()
+class SystemExitRestarter @Inject constructor() : Restarter {
+ override fun restart() {
+ System.exit(0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
index 18fb423..d9bcb50 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -50,13 +50,12 @@
@Override
public void accept(T extension) {
- try {
- Fragment.class.cast(extension);
+ if (Fragment.class.isInstance(extension)) {
mFragmentHostManager.getExtensionManager().setCurrentExtension(mId, mTag,
mOldClass, extension.getClass().getName(), mExtension.getContext());
mOldClass = extension.getClass().getName();
- } catch (ClassCastException e) {
- Log.e(TAG, extension.getClass().getName() + " must be a Fragment", e);
+ } else {
+ Log.e(TAG, extension.getClass().getName() + " must be a Fragment");
}
mExtension.clearItem(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3ef5499..db2cd91 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -201,6 +202,7 @@
protected final SecureSettings mSecureSettings;
protected final Resources mResources;
private final ConfigurationController mConfigurationController;
+ private final UserTracker mUserTracker;
private final UserManager mUserManager;
private final TrustManager mTrustManager;
private final IActivityManager mIActivityManager;
@@ -339,6 +341,7 @@
@NonNull VibratorHelper vibrator,
@Main Resources resources,
ConfigurationController configurationController,
+ UserTracker userTracker,
KeyguardStateController keyguardStateController,
UserManager userManager,
TrustManager trustManager,
@@ -370,6 +373,7 @@
mSecureSettings = secureSettings;
mResources = resources;
mConfigurationController = configurationController;
+ mUserTracker = userTracker;
mUserManager = userManager;
mTrustManager = trustManager;
mIActivityManager = iActivityManager;
@@ -1198,11 +1202,7 @@
}
protected UserInfo getCurrentUser() {
- try {
- return mIActivityManager.getCurrentUser();
- } catch (RemoteException re) {
- return null;
- }
+ return mUserTracker.getUserInfo();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 1f52fc6..9b2e6b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -375,7 +375,7 @@
public static final int INDICATION_TYPE_ALIGNMENT = 4;
public static final int INDICATION_TYPE_TRANSIENT = 5;
public static final int INDICATION_TYPE_TRUST = 6;
- public static final int INDICATION_TYPE_RESTING = 7;
+ public static final int INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
@@ -390,7 +390,7 @@
INDICATION_TYPE_ALIGNMENT,
INDICATION_TYPE_TRANSIENT,
INDICATION_TYPE_TRUST,
- INDICATION_TYPE_RESTING,
+ INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
INDICATION_TYPE_USER_LOCKED,
INDICATION_TYPE_REVERSE_CHARGING,
INDICATION_TYPE_BIOMETRIC_MESSAGE,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
new file mode 100644
index 0000000..bfc60c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -0,0 +1,329 @@
+/*
+ * 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.keyguard
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Log
+import com.android.systemui.SystemUIAppComponentFactoryBase
+import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import javax.inject.Inject
+
+class KeyguardQuickAffordanceProvider :
+ ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
+
+ @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
+
+ private lateinit var contextAvailableCallback: ContextAvailableCallback
+
+ private val uriMatcher =
+ UriMatcher(UriMatcher.NO_MATCH).apply {
+ addURI(
+ Contract.AUTHORITY,
+ Contract.SlotTable.TABLE_NAME,
+ MATCH_CODE_ALL_SLOTS,
+ )
+ addURI(
+ Contract.AUTHORITY,
+ Contract.AffordanceTable.TABLE_NAME,
+ MATCH_CODE_ALL_AFFORDANCES,
+ )
+ addURI(
+ Contract.AUTHORITY,
+ Contract.SelectionTable.TABLE_NAME,
+ MATCH_CODE_ALL_SELECTIONS,
+ )
+ addURI(
+ Contract.AUTHORITY,
+ Contract.FlagsTable.TABLE_NAME,
+ MATCH_CODE_ALL_FLAGS,
+ )
+ }
+
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun attachInfo(context: Context?, info: ProviderInfo?) {
+ contextAvailableCallback.onContextAvailable(checkNotNull(context))
+ super.attachInfo(context, info)
+ }
+
+ override fun setContextAvailableCallback(callback: ContextAvailableCallback) {
+ contextAvailableCallback = callback
+ }
+
+ override fun getType(uri: Uri): String? {
+ val prefix =
+ when (uriMatcher.match(uri)) {
+ MATCH_CODE_ALL_SLOTS,
+ MATCH_CODE_ALL_AFFORDANCES,
+ MATCH_CODE_ALL_FLAGS,
+ MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd."
+ else -> null
+ }
+
+ val tableName =
+ when (uriMatcher.match(uri)) {
+ MATCH_CODE_ALL_SLOTS -> Contract.SlotTable.TABLE_NAME
+ MATCH_CODE_ALL_AFFORDANCES -> Contract.AffordanceTable.TABLE_NAME
+ MATCH_CODE_ALL_SELECTIONS -> Contract.SelectionTable.TABLE_NAME
+ MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
+ else -> null
+ }
+
+ if (prefix == null || tableName == null) {
+ return null
+ }
+
+ return "$prefix${Contract.AUTHORITY}.$tableName"
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
+ throw UnsupportedOperationException()
+ }
+
+ return insertSelection(values)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<out String>?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ sortOrder: String?,
+ ): Cursor? {
+ return when (uriMatcher.match(uri)) {
+ MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
+ MATCH_CODE_ALL_SLOTS -> querySlots()
+ MATCH_CODE_ALL_SELECTIONS -> querySelections()
+ MATCH_CODE_ALL_FLAGS -> queryFlags()
+ else -> null
+ }
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int {
+ Log.e(TAG, "Update is not supported!")
+ return 0
+ }
+
+ override fun delete(
+ uri: Uri,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int {
+ if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
+ throw UnsupportedOperationException()
+ }
+
+ return deleteSelection(uri, selectionArgs)
+ }
+
+ private fun insertSelection(values: ContentValues?): Uri? {
+ if (values == null) {
+ throw IllegalArgumentException("Cannot insert selection, no values passed in!")
+ }
+
+ if (!values.containsKey(Contract.SelectionTable.Columns.SLOT_ID)) {
+ throw IllegalArgumentException(
+ "Cannot insert selection, " +
+ "\"${Contract.SelectionTable.Columns.SLOT_ID}\" not specified!"
+ )
+ }
+
+ if (!values.containsKey(Contract.SelectionTable.Columns.AFFORDANCE_ID)) {
+ throw IllegalArgumentException(
+ "Cannot insert selection, " +
+ "\"${Contract.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!"
+ )
+ }
+
+ val slotId = values.getAsString(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceId = values.getAsString(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+
+ if (slotId.isNullOrEmpty()) {
+ throw IllegalArgumentException("Cannot insert selection, slot ID was empty!")
+ }
+
+ if (affordanceId.isNullOrEmpty()) {
+ throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!")
+ }
+
+ val success =
+ interactor.select(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+
+ return if (success) {
+ Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
+ context?.contentResolver?.notifyChange(Contract.SelectionTable.URI, null)
+ Contract.SelectionTable.URI
+ } else {
+ Log.d(TAG, "Failed to select $affordanceId for slot $slotId")
+ null
+ }
+ }
+
+ private fun querySelections(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.SelectionTable.Columns.SLOT_ID,
+ Contract.SelectionTable.Columns.AFFORDANCE_ID,
+ Contract.SelectionTable.Columns.AFFORDANCE_NAME,
+ )
+ )
+ .apply {
+ val affordanceRepresentationsBySlotId = interactor.getSelections()
+ affordanceRepresentationsBySlotId.entries.forEach {
+ (slotId, affordanceRepresentations) ->
+ affordanceRepresentations.forEach { affordanceRepresentation ->
+ addRow(
+ arrayOf(
+ slotId,
+ affordanceRepresentation.id,
+ affordanceRepresentation.name,
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun queryAffordances(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.AffordanceTable.Columns.ID,
+ Contract.AffordanceTable.Columns.NAME,
+ Contract.AffordanceTable.Columns.ICON,
+ )
+ )
+ .apply {
+ interactor.getAffordancePickerRepresentations().forEach { representation ->
+ addRow(
+ arrayOf(
+ representation.id,
+ representation.name,
+ representation.iconResourceId,
+ )
+ )
+ }
+ }
+ }
+
+ private fun querySlots(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.SlotTable.Columns.ID,
+ Contract.SlotTable.Columns.CAPACITY,
+ )
+ )
+ .apply {
+ interactor.getSlotPickerRepresentations().forEach { representation ->
+ addRow(
+ arrayOf(
+ representation.id,
+ representation.maxSelectedAffordances,
+ )
+ )
+ }
+ }
+ }
+
+ private fun queryFlags(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.FlagsTable.Columns.NAME,
+ Contract.FlagsTable.Columns.VALUE,
+ )
+ )
+ .apply {
+ interactor.getPickerFlags().forEach { flag ->
+ addRow(
+ arrayOf(
+ flag.name,
+ if (flag.value) {
+ 1
+ } else {
+ 0
+ },
+ )
+ )
+ }
+ }
+ }
+
+ private fun deleteSelection(
+ uri: Uri,
+ selectionArgs: Array<out String>?,
+ ): Int {
+ if (selectionArgs == null) {
+ throw IllegalArgumentException(
+ "Cannot delete selection, selection arguments not included!"
+ )
+ }
+
+ val (slotId, affordanceId) =
+ when (selectionArgs.size) {
+ 1 -> Pair(selectionArgs[0], null)
+ 2 -> Pair(selectionArgs[0], selectionArgs[1])
+ else ->
+ throw IllegalArgumentException(
+ "Cannot delete selection, selection arguments has wrong size, expected to" +
+ " have 1 or 2 arguments, had ${selectionArgs.size} instead!"
+ )
+ }
+
+ val deleted =
+ interactor.unselect(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+
+ return if (deleted) {
+ Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId")
+ context?.contentResolver?.notifyChange(uri, null)
+ 1
+ } else {
+ Log.d(TAG, "Failed to unselect $affordanceId for slot $slotId")
+ 0
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceProvider"
+ private const val MATCH_CODE_ALL_SLOTS = 1
+ private const val MATCH_CODE_ALL_AFFORDANCES = 2
+ private const val MATCH_CODE_ALL_SELECTIONS = 3
+ private const val MATCH_CODE_ALL_FLAGS = 4
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 5d564f7..bafd2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import android.annotation.AnyThread;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -52,6 +51,7 @@
import com.android.systemui.R;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -140,6 +140,8 @@
public KeyguardBypassController mKeyguardBypassController;
@Inject
public KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Inject
+ UserTracker mUserTracker;
private CharSequence mMediaTitle;
private CharSequence mMediaArtist;
protected boolean mDozing;
@@ -355,7 +357,7 @@
synchronized (this) {
if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
- ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm";
+ mUserTracker.getUserId()) ? "HH:mm" : "h:mm";
mNextAlarm = android.text.format.DateFormat.format(pattern,
mNextAlarmInfo.getTriggerTime()).toString();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 04a74ce..5ed3ba7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -36,7 +36,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -124,6 +123,7 @@
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -137,6 +137,7 @@
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -263,6 +264,7 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
+ private final UserTracker mUserTracker;
private final SysuiStatusBarStateController mStatusBarStateController;
private final Executor mUiBgExecutor;
private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -409,6 +411,11 @@
private final int mDreamOpenAnimationDuration;
/**
+ * The duration in milliseconds of the dream close animation.
+ */
+ private final int mDreamCloseAnimationDuration;
+
+ /**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
*/
@@ -715,7 +722,7 @@
@Override
public void keyguardDone(boolean strongAuth, int targetUserId) {
- if (targetUserId != ActivityManager.getCurrentUser()) {
+ if (targetUserId != mUserTracker.getUserId()) {
return;
}
if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -738,7 +745,7 @@
public void keyguardDonePending(boolean strongAuth, int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
if (DEBUG) Log.d(TAG, "keyguardDonePending");
- if (targetUserId != ActivityManager.getCurrentUser()) {
+ if (targetUserId != mUserTracker.getUserId()) {
Trace.endSection();
return;
}
@@ -846,6 +853,7 @@
@Override
public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
mOccludeAnimationPlaying = true;
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
}
@Override
@@ -856,6 +864,7 @@
// Ensure keyguard state is set correctly if we're cancelled.
mCentralSurfaces.updateIsKeyguard();
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
}
@Override
@@ -869,6 +878,7 @@
// Hide the keyguard now that we're done launching the occluding activity over
// it.
mCentralSurfaces.updateIsKeyguard();
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
}
@@ -890,25 +900,32 @@
@NonNull
@Override
public LaunchAnimator.State createAnimatorState() {
- final int width = getLaunchContainer().getWidth();
- final int height = getLaunchContainer().getHeight();
-
- final float initialHeight = height / 3f;
- final float initialWidth = width / 3f;
+ final int fullWidth = getLaunchContainer().getWidth();
+ final int fullHeight = getLaunchContainer().getHeight();
if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
+ final float initialHeight = fullHeight / 3f;
+ final float initialWidth = fullWidth / 3f;
+
// Start the animation near the power button, at one-third size, since the
// camera was launched from the power button.
return new LaunchAnimator.State(
(int) (mPowerButtonY - initialHeight / 2f) /* top */,
(int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
- (int) (width - initialWidth) /* left */,
- width /* right */,
+ (int) (fullWidth - initialWidth) /* left */,
+ fullWidth /* right */,
mWindowCornerRadius, mWindowCornerRadius);
} else {
- // Start the animation in the center of the screen, scaled down.
+ final float initialHeight = fullHeight / 2f;
+ final float initialWidth = fullWidth / 2f;
+
+ // Start the animation in the center of the screen, scaled down to half
+ // size.
return new LaunchAnimator.State(
- height / 2, height / 2, width / 2, width / 2,
+ (int) (fullHeight - initialHeight) / 2,
+ (int) (initialHeight + (fullHeight - initialHeight) / 2),
+ (int) (fullWidth - initialWidth) / 2,
+ (int) (initialWidth + (fullWidth - initialWidth) / 2),
mWindowCornerRadius, mWindowCornerRadius);
}
}
@@ -1055,7 +1072,8 @@
}
mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f);
- mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+ mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration
+ : UNOCCLUDE_ANIMATION_DURATION);
mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE);
mUnoccludeAnimator.addUpdateListener(
animation -> {
@@ -1125,12 +1143,14 @@
private ScreenOnCoordinator mScreenOnCoordinator;
private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private Lazy<ScrimController> mScrimControllerLazy;
/**
* Injected constructor. See {@link KeyguardModule}.
*/
public KeyguardViewMediator(
Context context,
+ UserTracker userTracker,
FalsingCollector falsingCollector,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -1154,8 +1174,10 @@
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ScrimController> scrimControllerLazy) {
mContext = context;
+ mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
mLockPatternUtils = lockPatternUtils;
mBroadcastDispatcher = broadcastDispatcher;
@@ -1198,6 +1220,7 @@
mDreamOverlayStateController = dreamOverlayStateController;
mActivityLaunchAnimator = activityLaunchAnimator;
+ mScrimControllerLazy = scrimControllerLazy;
mPowerButtonY = context.getResources().getDimensionPixelSize(
R.dimen.physical_power_button_center_screen_location_y);
@@ -1205,6 +1228,8 @@
mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration);
+ mDreamCloseAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_dreamCloseAnimationDuration);
}
public void userActivity() {
@@ -1234,7 +1259,7 @@
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
+ KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
// Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
// is disabled.
@@ -1729,7 +1754,7 @@
try {
callback.onKeyguardExitResult(true);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+ Slog.w(TAG, "Failed to call onKeyguardExitResult(true)", e);
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 78a7c9e..47ef0fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,12 +47,14 @@
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -86,6 +88,7 @@
@SysUISingleton
public static KeyguardViewMediator newKeyguardViewMediator(
Context context,
+ UserTracker userTracker,
FalsingCollector falsingCollector,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -111,9 +114,11 @@
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ScrimController> scrimControllerLazy) {
return new KeyguardViewMediator(
context,
+ userTracker,
falsingCollector,
lockPatternUtils,
broadcastDispatcher,
@@ -139,7 +144,8 @@
dreamOverlayStateController,
shadeController,
notificationShadeWindowController,
- activityLaunchAnimator);
+ activityLaunchAnimator,
+ scrimControllerLazy);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index a069582..f5220b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -24,6 +24,7 @@
*/
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
+ const val CAMERA = "camera"
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000..3c09aab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import javax.inject.Inject
+
+@SysUISingleton
+class CameraQuickAffordanceConfig @Inject constructor(
+ @Application private val context: Context,
+ private val cameraGestureHelper: CameraGestureHelper,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.CAMERA
+
+ override val pickerName: String
+ get() = context.getString(R.string.accessibility_camera_button)
+
+ override val pickerIconResourceId: Int
+ get() = com.android.internal.R.drawable.perm_group_camera
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+ get() = flowOf(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = Icon.Resource(
+ com.android.internal.R.drawable.perm_group_camera,
+ ContentDescription.Resource(R.string.accessibility_camera_button)
+ )
+ )
+ )
+
+ override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index bea9363..f7225a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -29,8 +29,10 @@
home: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ camera: CameraQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
+ camera,
home,
quickAccessWallet,
qrCodeScanner,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
new file mode 100644
index 0000000..766096f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
@@ -0,0 +1,214 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer.Companion.BINDINGS
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Keeps quick affordance selections and legacy user settings in sync.
+ *
+ * "Legacy user settings" are user settings like: Settings > Display > Lock screen > "Show device
+ * controls" Settings > Display > Lock screen > "Show wallet"
+ *
+ * Quick affordance selections are the ones available through the new custom lock screen experience
+ * from Settings > Wallpaper & Style.
+ *
+ * This class keeps these in sync, mostly for backwards compatibility purposes and in order to not
+ * "forget" an existing legacy user setting when the device gets updated with a version of System UI
+ * that has the new customizable lock screen feature.
+ *
+ * The way it works is that, when [startSyncing] is called, the syncer starts coroutines to listen
+ * for changes in both legacy user settings and their respective affordance selections. Whenever one
+ * of each pair is changed, the other member of that pair is also updated to match. For example, if
+ * the user turns on "Show device controls", we automatically select the home controls affordance
+ * for the preferred slot. Conversely, when the home controls affordance is unselected by the user,
+ * we set the "Show device controls" setting to "off".
+ *
+ * The class can be configured by updating its list of triplets in the code under [BINDINGS].
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceLegacySettingSyncer
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val secureSettings: SecureSettings,
+ private val selectionsManager: KeyguardQuickAffordanceSelectionManager,
+) {
+ companion object {
+ private val BINDINGS =
+ listOf(
+ Binding(
+ settingsKey = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ ),
+ Binding(
+ settingsKey = Settings.Secure.LOCKSCREEN_SHOW_WALLET,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+ ),
+ Binding(
+ settingsKey = Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER,
+ ),
+ )
+ }
+
+ fun startSyncing(
+ bindings: List<Binding> = BINDINGS,
+ ): Job {
+ return scope.launch { bindings.forEach { binding -> startSyncing(this, binding) } }
+ }
+
+ private fun startSyncing(
+ scope: CoroutineScope,
+ binding: Binding,
+ ) {
+ secureSettings
+ .observerFlow(
+ names = arrayOf(binding.settingsKey),
+ userId = UserHandle.USER_ALL,
+ )
+ .map {
+ isSet(
+ settingsKey = binding.settingsKey,
+ )
+ }
+ .distinctUntilChanged()
+ .onEach { isSet ->
+ if (isSelected(binding.affordanceId) != isSet) {
+ if (isSet) {
+ select(
+ slotId = binding.slotId,
+ affordanceId = binding.affordanceId,
+ )
+ } else {
+ unselect(
+ affordanceId = binding.affordanceId,
+ )
+ }
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .launchIn(scope)
+
+ selectionsManager.selections
+ .map { it.values.flatten().toSet() }
+ .map { it.contains(binding.affordanceId) }
+ .distinctUntilChanged()
+ .onEach { isSelected ->
+ if (isSet(binding.settingsKey) != isSelected) {
+ set(binding.settingsKey, isSelected)
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .launchIn(scope)
+ }
+
+ private fun isSelected(
+ affordanceId: String,
+ ): Boolean {
+ return selectionsManager
+ .getSelections() // Map<String, List<String>>
+ .values // Collection<List<String>>
+ .flatten() // List<String>
+ .toSet() // Set<String>
+ .contains(affordanceId)
+ }
+
+ private fun select(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ val affordanceIdsAtSlotId = selectionsManager.getSelections()[slotId] ?: emptyList()
+ selectionsManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIdsAtSlotId + listOf(affordanceId),
+ )
+ }
+
+ private fun unselect(
+ affordanceId: String,
+ ) {
+ val currentSelections = selectionsManager.getSelections()
+ val slotIdsContainingAffordanceId =
+ currentSelections
+ .filter { (_, affordanceIds) -> affordanceIds.contains(affordanceId) }
+ .map { (slotId, _) -> slotId }
+
+ slotIdsContainingAffordanceId.forEach { slotId ->
+ val currentAffordanceIds = currentSelections[slotId] ?: emptyList()
+ val affordanceIdsAfterUnselecting =
+ currentAffordanceIds.toMutableList().apply { remove(affordanceId) }
+
+ selectionsManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIdsAfterUnselecting,
+ )
+ }
+ }
+
+ private fun isSet(
+ settingsKey: String,
+ ): Boolean {
+ return secureSettings.getIntForUser(
+ settingsKey,
+ 0,
+ UserHandle.USER_CURRENT,
+ ) != 0
+ }
+
+ private suspend fun set(
+ settingsKey: String,
+ isSet: Boolean,
+ ) {
+ withContext(backgroundDispatcher) {
+ secureSettings.putInt(
+ settingsKey,
+ if (isSet) 1 else 0,
+ )
+ }
+ }
+
+ data class Binding(
+ val settingsKey: String,
+ val slotId: String,
+ val affordanceId: String,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index 9c9354f..b29cf45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -17,46 +17,138 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
/**
* Manages and provides access to the current "selections" of keyguard quick affordances, answering
* the question "which affordances should the keyguard show?".
*/
@SysUISingleton
-class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
+class KeyguardQuickAffordanceSelectionManager
+@Inject
+constructor(
+ @Application context: Context,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+) {
- // TODO(b/254858695): implement a persistence layer (database).
- private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+ private val sharedPrefs: SharedPreferences
+ get() =
+ userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+ private val defaults: Map<String, List<String>> by lazy {
+ context.resources
+ .getStringArray(R.array.config_keyguardQuickAffordanceDefaults)
+ .associate { item ->
+ val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER)
+ check(splitUp.size == 2)
+ val slotId = splitUp[0]
+ val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER)
+ slotId to affordanceIds
+ }
+ }
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
- val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+ val selections: Flow<Map<String, List<String>>> =
+ userId.flatMapLatest {
+ conflatedCallbackFlow {
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
* descending priority order.
*/
- suspend fun getSelections(): Map<String, List<String>> {
- return _selections.value
+ fun getSelections(): Map<String, List<String>> {
+ val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
+ val result =
+ slotKeys
+ .associate { key ->
+ val slotId = key.substring(KEY_PREFIX_SLOT.length)
+ val value = sharedPrefs.getString(key, null)
+ val affordanceIds =
+ if (!value.isNullOrEmpty()) {
+ value.split(AFFORDANCE_DELIMITER)
+ } else {
+ emptyList()
+ }
+ slotId to affordanceIds
+ }
+ .toMutableMap()
+
+ // If the result map is missing keys, it means that the system has never set anything for
+ // those slots. This is where we need examine our defaults and see if there should be a
+ // default value for the affordances in the slot IDs that are missing from the result.
+ //
+ // Once the user makes any selection for a slot, even when they select "None", this class
+ // will persist a key for that slot ID. In the case of "None", it will have a value of the
+ // empty string. This is why this system works.
+ defaults.forEach { (slotId, affordanceIds) ->
+ if (!result.containsKey(slotId)) {
+ result[slotId] = affordanceIds
+ }
+ }
+
+ return result
}
/**
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
* IDs should be descending priority order.
*/
- suspend fun setSelections(
+ fun setSelections(
slotId: String,
affordanceIds: List<String>,
) {
- // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
- // when we set its value to the same instance of the original map, even if we change the
- // map by updating the value of one of its keys.
- val copy = _selections.value.toMutableMap()
- copy[slotId] = affordanceIds
- _selections.value = copy
+ val key = "$KEY_PREFIX_SLOT$slotId"
+ val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
+ sharedPrefs.edit().putString(key, value).apply()
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceSelectionManager"
+ @VisibleForTesting const val FILE_NAME = "quick_affordance_selections"
+ private const val KEY_PREFIX_SLOT = "slot_"
+ private const val SLOT_AFFORDANCES_DELIMITER = ":"
+ private const val AFFORDANCE_DELIMITER = ","
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 0046256..783f752 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -16,9 +16,7 @@
package com.android.systemui.keyguard.data.repository
-import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
@@ -28,7 +26,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-/** Encapsulates app state for the lock screen bouncer. */
+/** Encapsulates app state for the lock screen primary and alternate bouncer. */
@SysUISingleton
class KeyguardBouncerRepository
@Inject
@@ -36,12 +34,24 @@
private val viewMediatorCallback: ViewMediatorCallback,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
- var bouncerPromptReason: Int? = null
- /** Determines if we want to instantaneously show the bouncer instead of translating. */
- private val _isScrimmed = MutableStateFlow(false)
- val isScrimmed = _isScrimmed.asStateFlow()
+ /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+ private val _primaryBouncerVisible = MutableStateFlow(false)
+ val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+ private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+ val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+ private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+ val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+ private val _primaryBouncerHide = MutableStateFlow(false)
+ val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+ private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+ val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+ private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+ val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
+ /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+ private val _primaryBouncerScrimmed = MutableStateFlow(false)
+ val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
/**
- * Set how much of the panel is showing on the screen.
+ * Set how much of the notification panel is showing on the screen.
* ```
* 0f = panel fully hidden = bouncer fully showing
* 1f = panel fully showing = bouncer fully hidden
@@ -49,84 +59,56 @@
*/
private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
- private val _isVisible = MutableStateFlow(false)
- val isVisible = _isVisible.asStateFlow()
- private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
- val show = _show.asStateFlow()
- private val _showingSoon = MutableStateFlow(false)
- val showingSoon = _showingSoon.asStateFlow()
- private val _hide = MutableStateFlow(false)
- val hide = _hide.asStateFlow()
- private val _startingToHide = MutableStateFlow(false)
- val startingToHide = _startingToHide.asStateFlow()
- private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
- val startingDisappearAnimation = _disappearAnimation.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
val keyguardPosition = _keyguardPosition.asStateFlow()
- private val _resourceUpdateRequests = MutableStateFlow(false)
- val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
- private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
- val showMessage = _showMessage.asStateFlow()
+ private val _onScreenTurnedOff = MutableStateFlow(false)
+ val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+ private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+ val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
/** Determines if user is already unlocked */
val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
- private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
- val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
- private val _onScreenTurnedOff = MutableStateFlow(false)
- val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
-
+ private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+ val showMessage = _showMessage.asStateFlow()
+ private val _resourceUpdateRequests = MutableStateFlow(false)
+ val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+ val bouncerPromptReason: Int
+ get() = viewMediatorCallback.bouncerPromptReason
val bouncerErrorMessage: CharSequence?
get() = viewMediatorCallback.consumeCustomMessage()
- init {
- val callback =
- object : KeyguardUpdateMonitorCallback() {
- override fun onStrongAuthStateChanged(userId: Int) {
- bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
- }
-
- override fun onLockedOutStateChanged(type: BiometricSourceType) {
- if (type == BiometricSourceType.FINGERPRINT) {
- bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
- }
- }
- }
-
- keyguardUpdateMonitor.registerCallback(callback)
+ fun setPrimaryScrimmed(isScrimmed: Boolean) {
+ _primaryBouncerScrimmed.value = isScrimmed
}
- fun setScrimmed(isScrimmed: Boolean) {
- _isScrimmed.value = isScrimmed
+ fun setPrimaryVisible(isVisible: Boolean) {
+ _primaryBouncerVisible.value = isVisible
+ }
+
+ fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+ _primaryBouncerShow.value = keyguardBouncerModel
+ }
+
+ fun setPrimaryShowingSoon(showingSoon: Boolean) {
+ _primaryBouncerShowingSoon.value = showingSoon
+ }
+
+ fun setPrimaryHide(hide: Boolean) {
+ _primaryBouncerHide.value = hide
+ }
+
+ fun setPrimaryStartingToHide(startingToHide: Boolean) {
+ _primaryBouncerStartingToHide.value = startingToHide
+ }
+
+ fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+ _primaryBouncerDisappearAnimation.value = runnable
}
fun setPanelExpansion(panelExpansion: Float) {
_panelExpansionAmount.value = panelExpansion
}
- fun setVisible(isVisible: Boolean) {
- _isVisible.value = isVisible
- }
-
- fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) {
- _show.value = keyguardBouncerModel
- }
-
- fun setShowingSoon(showingSoon: Boolean) {
- _showingSoon.value = showingSoon
- }
-
- fun setHide(hide: Boolean) {
- _hide.value = hide
- }
-
- fun setStartingToHide(startingToHide: Boolean) {
- _startingToHide.value = startingToHide
- }
-
- fun setStartDisappearAnimation(runnable: Runnable?) {
- _disappearAnimation.value = runnable
- }
-
fun setKeyguardPosition(keyguardPosition: Float) {
_keyguardPosition.value = keyguardPosition
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 95f614f..533b3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -17,31 +17,31 @@
package com.android.systemui.keyguard.data.repository
+import android.content.Context
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/** Abstracts access to application state related to keyguard quick affordances. */
@SysUISingleton
class KeyguardQuickAffordanceRepository
@Inject
constructor(
+ @Application private val appContext: Context,
@Application private val scope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
) {
/**
@@ -61,11 +61,39 @@
initialValue = emptyMap(),
)
+ private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
+ fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+ val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+ check(split.size == 2)
+ val slotId = split[0]
+ val slotCapacity = split[1].toInt()
+ return slotId to slotCapacity
+ }
+
+ val unparsedSlots =
+ appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+
+ val seenSlotIds = mutableSetOf<String>()
+ unparsedSlots.mapNotNull { unparsedSlot ->
+ val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+ check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+ seenSlotIds.add(slotId)
+ KeyguardSlotPickerRepresentation(
+ id = slotId,
+ maxSelectedAffordances = slotCapacity,
+ )
+ }
+ }
+
+ init {
+ legacySettingSyncer.startSyncing()
+ }
+
/**
* Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
* slot with the given ID. The configs are sorted in descending priority order.
*/
- suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
@@ -74,7 +102,7 @@
* Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
* are sorted in descending priority order.
*/
- suspend fun getSelections(): Map<String, List<String>> {
+ fun getSelections(): Map<String, List<String>> {
return selectionManager.getSelections()
}
@@ -86,12 +114,10 @@
slotId: String,
affordanceIds: List<String>,
) {
- scope.launch(backgroundDispatcher) {
- selectionManager.setSelections(
- slotId = slotId,
- affordanceIds = affordanceIds,
- )
- }
+ selectionManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIds,
+ )
}
/**
@@ -115,14 +141,10 @@
* each slot and select which affordance(s) is/are installed in each slot on the keyguard.
*/
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
- // TODO(b/256195304): source these from a config XML file.
- return listOf(
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- ),
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- ),
- )
+ return _slotPickerRepresentations
+ }
+
+ companion object {
+ private const val SLOT_CONFIG_DELIMITER = ":"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index c867c6e..796f2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -16,14 +16,21 @@
package com.android.systemui.keyguard.data.repository
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -68,6 +75,9 @@
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Observable for the signal that keyguard is about to go away. */
+ val isKeyguardGoingAway: Flow<Boolean>
+
/** Observable for whether the bouncer is showing. */
val isBouncerShowing: Flow<Boolean>
@@ -84,6 +94,14 @@
val isDozing: Flow<Boolean>
/**
+ * Observable for whether the device is dreaming.
+ *
+ * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams
+ * to be active, such as screensavers.
+ */
+ val isDreaming: Flow<Boolean>
+
+ /**
* Observable for the amount of doze we are currently in.
*
* While in doze state, this amount can change - driving a cycle of animations designed to avoid
@@ -95,6 +113,9 @@
*/
val dozeAmount: Flow<Float>
+ /** Doze state information, as it transitions */
+ val dozeTransitionModel: Flow<DozeTransitionModel>
+
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState>
@@ -123,6 +144,11 @@
* Sets the relative offset of the lock-screen clock from its natural position on the screen.
*/
fun setClockPosition(x: Int, y: Int)
+
+ /**
+ * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+ */
+ fun isUdfpsSupported(): Boolean
}
/** Encapsulates application state for the keyguard. */
@@ -131,10 +157,12 @@
@Inject
constructor(
statusBarStateController: StatusBarStateController,
- private val keyguardStateController: KeyguardStateController,
dozeHost: DozeHost,
wakefulnessLifecycle: WakefulnessLifecycle,
biometricUnlockController: BiometricUnlockController,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dozeTransitionListener: DozeTransitionListener,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -169,6 +197,29 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardGoingAwayChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isKeyguardGoingAway,
+ TAG,
+ "updated isKeyguardGoingAway"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isKeyguardGoingAway,
+ TAG,
+ "initial isKeyguardGoingAway"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
@@ -211,6 +262,25 @@
}
.distinctUntilChanged()
+ override val isDreaming: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onDreamingStateChanged(isDreaming: Boolean) {
+ trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(
+ keyguardUpdateMonitor.isDreaming,
+ TAG,
+ "initial isDreaming",
+ )
+
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+
override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
val callback =
object : StatusBarStateController.StateListener {
@@ -225,6 +295,37 @@
awaitClose { statusBarStateController.removeCallback(callback) }
}
+ override val dozeTransitionModel: Flow<DozeTransitionModel> = conflatedCallbackFlow {
+ val callback =
+ object : DozeTransitionCallback {
+ override fun onDozeTransition(
+ oldState: DozeMachine.State,
+ newState: DozeMachine.State
+ ) {
+ trySendWithFailureLogging(
+ DozeTransitionModel(
+ from = dozeMachineStateToModel(oldState),
+ to = dozeMachineStateToModel(newState),
+ ),
+ TAG,
+ "doze transition model"
+ )
+ }
+ }
+
+ dozeTransitionListener.addCallback(callback)
+ trySendWithFailureLogging(
+ DozeTransitionModel(
+ from = dozeMachineStateToModel(dozeTransitionListener.oldState),
+ to = dozeMachineStateToModel(dozeTransitionListener.newState),
+ ),
+ TAG,
+ "initial doze transition model"
+ )
+
+ awaitClose { dozeTransitionListener.removeCallback(callback) }
+ }
+
override fun isKeyguardShowing(): Boolean {
return keyguardStateController.isShowing
}
@@ -311,6 +412,8 @@
_clockPosition.value = Position(x, y)
}
+ override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
+
private fun statusBarStateIntToObject(value: Int): StatusBarState {
return when (value) {
0 -> StatusBarState.SHADE
@@ -344,6 +447,25 @@
}
}
+ private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
+ return when (state) {
+ DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
+ DozeMachine.State.INITIALIZED -> DozeStateModel.INITIALIZED
+ DozeMachine.State.DOZE -> DozeStateModel.DOZE
+ DozeMachine.State.DOZE_SUSPEND_TRIGGERS -> DozeStateModel.DOZE_SUSPEND_TRIGGERS
+ DozeMachine.State.DOZE_AOD -> DozeStateModel.DOZE_AOD
+ DozeMachine.State.DOZE_REQUEST_PULSE -> DozeStateModel.DOZE_REQUEST_PULSE
+ DozeMachine.State.DOZE_PULSING -> DozeStateModel.DOZE_PULSING
+ DozeMachine.State.DOZE_PULSING_BRIGHT -> DozeStateModel.DOZE_PULSING_BRIGHT
+ DozeMachine.State.DOZE_PULSE_DONE -> DozeStateModel.DOZE_PULSE_DONE
+ DozeMachine.State.FINISH -> DozeStateModel.FINISH
+ DozeMachine.State.DOZE_AOD_PAUSED -> DozeStateModel.DOZE_AOD_PAUSED
+ DozeMachine.State.DOZE_AOD_PAUSING -> DozeStateModel.DOZE_AOD_PAUSING
+ DozeMachine.State.DOZE_AOD_DOCKED -> DozeStateModel.DOZE_AOD_DOCKED
+ else -> throw IllegalArgumentException("Invalid DozeMachine.State: state")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
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 e3d1a27..bce7d92 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
@@ -94,11 +94,13 @@
*/
private val _transitions =
MutableSharedFlow<TransitionStep>(
+ replay = 2,
extraBufferCapacity = 10,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
private var lastStep: TransitionStep = TransitionStep()
+ private var lastAnimator: ValueAnimator? = null
/*
* When manual control of the transition is requested, a unique [UUID] is used as the handle
@@ -106,19 +108,39 @@
*/
private var updateTransitionId: UUID? = null
+ init {
+ // Seed with transitions signaling a boot into lockscreen state
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ 0f,
+ TransitionState.STARTED,
+ )
+ )
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ 1f,
+ TransitionState.FINISHED,
+ )
+ )
+ }
+
override fun startTransition(info: TransitionInfo): UUID? {
if (lastStep.transitionState != TransitionState.FINISHED) {
- // Open questions:
- // * Queue of transitions? buffer of 1?
- // * Are transitions cancellable if a new one is triggered?
- // * What validation does this need to do?
- Log.wtf(TAG, "Transition still active: $lastStep")
- return null
+ Log.i(TAG, "Transition still active: $lastStep, canceling")
}
+ val startingValue = 1f - lastStep.value
+ lastAnimator?.cancel()
+ lastAnimator = info.animator
+
info.animator?.let { animator ->
// An animator was provided, so use it to run the transition
- animator.setFloatValues(0f, 1f)
+ animator.setFloatValues(startingValue, 1f)
+ animator.duration = ((1f - startingValue) * animator.duration).toLong()
val updateListener =
object : AnimatorUpdateListener {
override fun onAnimationUpdate(animation: ValueAnimator) {
@@ -134,15 +156,24 @@
val adapter =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
- emitTransition(TransitionStep(info, 0f, TransitionState.STARTED))
+ emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
}
override fun onAnimationCancel(animation: Animator) {
- Log.i(TAG, "Cancelling transition: $info")
+ endAnimation(animation, lastStep.value, TransitionState.CANCELED)
}
override fun onAnimationEnd(animation: Animator) {
- emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED))
+ endAnimation(animation, 1f, TransitionState.FINISHED)
+ }
+
+ private fun endAnimation(
+ animation: Animator,
+ value: Float,
+ state: TransitionState
+ ) {
+ emitTransition(TransitionStep(info, value, state))
animator.removeListener(this)
animator.removeUpdateListener(updateListener)
+ lastAnimator = null
}
}
animator.addListener(adapter)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index 0aeff7f..2dbacd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -20,8 +20,8 @@
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
@@ -35,18 +35,27 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
- private val keyguardRepository: KeyguardRepository,
+ private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("AOD<->LOCKSCREEN") {
+) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
override fun start() {
+ listenForTransitionToAodFromLockscreen()
+ listenForTransitionToLockscreenFromAod()
+ }
+
+ private fun listenForTransitionToAodFromLockscreen() {
scope.launch {
- keyguardRepository.isDozing
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+ .sample(
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ { a, b -> Pair(a, b) }
+ )
.collect { pair ->
- val (isDozing, keyguardState) = pair
- if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) {
+ val (dozeToAod, lastStartedStep) = pair
+ if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -55,7 +64,22 @@
getAnimator(),
)
)
- } else if (!isDozing && keyguardState == KeyguardState.AOD) {
+ }
+ }
+ }
+ }
+
+ private fun listenForTransitionToLockscreenFromAod() {
+ scope.launch {
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.FINISH)
+ .sample(
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ { a, b -> Pair(a, b) }
+ )
+ .collect { pair ->
+ val (dozeToAod, lastStartedStep) = pair
+ if (lastStartedStep.to == KeyguardState.AOD) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
index 7e01db3..2a220fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -40,7 +40,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("AOD->GONE") {
+) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
private val wakeAndUnlockModes =
setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..056c44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+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.TransitionInfo
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class BouncerToGoneTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val shadeRepository: ShadeRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor
+) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
+
+ private var transitionId: UUID? = null
+
+ override fun start() {
+ listenForKeyguardGoingAway()
+ }
+
+ private fun listenForKeyguardGoingAway() {
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isKeyguardGoingAway, keyguardState) = pair
+ if (isKeyguardGoingAway && keyguardState == KeyguardState.BOUNCER) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.BOUNCER,
+ to = KeyguardState.GONE,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
new file mode 100644
index 0000000..9cbf9ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingLockscreenTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(DreamingLockscreenTransitionInteractor::class.simpleName!!) {
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.isDreaming
+ .sample(
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.finishedKeyguardState
+ ) { a, b -> Pair(a, b) },
+ { a, bc -> Triple(a, bc.first, bc.second) }
+ )
+ .collect { triple ->
+ val (isDreaming, dozeTransitionModel, keyguardState) = triple
+ // Dozing/AOD and dreaming have overlapping events. If the state remains in
+ // FINISH, it means that doze mode is not running and DREAMING is ok to
+ // commence.
+ if (dozeTransitionModel.to == DozeStateModel.FINISH) {
+ if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.DREAMING,
+ getAnimator(),
+ )
+ )
+ } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
new file mode 100644
index 0000000..9e2b724
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+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.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingToAodTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("DREAMING->AOD") {
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.wakefulnessState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (wakefulnessState, keyguardState) = pair
+ if (
+ isSleepingOrStartingToSleep(wakefulnessState) &&
+ keyguardState == KeyguardState.DREAMING
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.AOD,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index ede50b0..d2a7486 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -48,4 +48,9 @@
fun setAnimateDozingTransitions(animate: Boolean) {
repository.setAnimateDozingTransitions(animate)
}
+
+ /**
+ * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+ */
+ fun shouldConstrainToTopOfLockIcon(): Boolean = repository.isUdfpsSupported()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 614ff8d..7cfd117 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -20,10 +20,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -41,8 +44,17 @@
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+ /** Doze transition information. */
+ val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
+ /**
+ * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
+ * but not vice-versa.
+ */
+ val isDreaming: Flow<Boolean> = repository.isDreaming
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the keyguard is going away. */
+ val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Whether the bouncer is showing or not. */
val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
@@ -55,6 +67,10 @@
*/
val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+ fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
+ return dozeTransitionModel.filter { it.to == state }
+ }
+
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
}
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 92caa89..c8216c5 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
@@ -28,11 +28,13 @@
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardPickerFlag
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
@@ -117,7 +119,7 @@
*
* @return `true` if the affordance was selected successfully; `false` otherwise.
*/
- suspend fun select(slotId: String, affordanceId: String): Boolean {
+ fun select(slotId: String, affordanceId: String): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
@@ -152,7 +154,7 @@
* @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
* the affordance was not on the slot to begin with).
*/
- suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+ fun unselect(slotId: String, affordanceId: String?): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
@@ -187,12 +189,18 @@
}
/** Returns affordance IDs indexed by slot ID, for all known slots. */
- suspend fun getSelections(): Map<String, List<String>> {
+ fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
check(isUsingRepository)
+ val slots = repository.get().getSlotPickerRepresentations()
val selections = repository.get().getSelections()
- return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
- slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
+ val affordanceById =
+ getAffordancePickerRepresentations().associateBy { affordance -> affordance.id }
+ return slots.associate { slot ->
+ slot.id to
+ (selections[slot.id] ?: emptyList()).mapNotNull { affordanceId ->
+ affordanceById[affordanceId]
+ }
}
}
@@ -314,6 +322,15 @@
return repository.get().getSlotPickerRepresentations()
}
+ fun getPickerFlags(): List<KeyguardPickerFlag> {
+ return listOf(
+ KeyguardPickerFlag(
+ name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+ value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
+ )
+ )
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceInteractor"
private const val DELIMITER = "::"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 57fb4a1..58a8093 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,12 +41,24 @@
}
scope.launch {
+ keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) }
+ }
+
+ scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+
+ scope.launch {
interactor.finishedKeyguardTransitionStep.collect {
logger.i("Finished transition", it)
}
}
scope.launch {
+ interactor.canceledKeyguardTransitionStep.collect {
+ logger.i("Canceled transition", it)
+ }
+ }
+
+ scope.launch {
interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index a7c6d44..43dd358e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -42,6 +42,9 @@
is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+ is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
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 749183e..54a4f49 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
@@ -57,6 +57,14 @@
lockscreenToAodTransition,
)
+ /* The last [TransitionStep] with a [TransitionState] of STARTED */
+ val startedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
+
+ /* The last [TransitionStep] with a [TransitionState] of CANCELED */
+ val canceledKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED }
+
/* The last [TransitionStep] with a [TransitionState] of FINISHED */
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
@@ -64,8 +72,4 @@
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
finishedKeyguardTransitionStep.map { step -> step.to }
-
- /* The last [TransitionStep] with a [TransitionState] of STARTED */
- val startedKeyguardTransitionStep: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index fd4814d..3bb8241 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -44,7 +44,7 @@
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
private var transitionId: UUID? = null
@@ -56,10 +56,20 @@
private fun listenForBouncerHiding() {
scope.launch {
keyguardInteractor.isBouncerShowing
- .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
- .collect { pair ->
- val (isBouncerShowing, wakefulnessState) = pair
- if (!isBouncerShowing) {
+ .sample(
+ combine(
+ keyguardInteractor.wakefulnessState,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ) { a, b ->
+ Pair(a, b)
+ },
+ { a, bc -> Triple(a, bc.first, bc.second) }
+ )
+ .collect { triple ->
+ val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+ if (
+ !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
+ ) {
val to =
if (
wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
@@ -90,10 +100,10 @@
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.statusBarState,
- ) { keyguardState, statusBarState ->
- Pair(keyguardState, statusBarState)
+ ) { a, b ->
+ Pair(a, b)
},
- { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) }
+ { a, bc -> Triple(a, bc.first, bc.second) }
)
.collect { triple ->
val (shadeModel, keyguardState, statusBarState) = triple
@@ -116,8 +126,7 @@
)
} else {
// TODO (b/251849525): Remove statusbarstate check when that state is
- // integrated
- // into KeyguardTransitionRepository
+ // integrated into KeyguardTransitionRepository
if (
keyguardState == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
index 6c1adbd..4100f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
@@ -23,6 +23,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.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
@@ -34,23 +35,27 @@
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
) : TransitionInteractor("LOCKSCREEN->GONE") {
override fun start() {
scope.launch {
- keyguardInteractor.isKeyguardShowing.collect { isShowing ->
- if (!isShowing) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.LOCKSCREEN,
- KeyguardState.GONE,
- getAnimator(),
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isKeyguardGoingAway, keyguardState) = pair
+ if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
)
- )
+ }
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
index 10c7a37..c5e49c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
@@ -24,9 +24,9 @@
/** Interactor to add and remove callbacks for the bouncer. */
@SysUISingleton
-class BouncerCallbackInteractor @Inject constructor() {
+class PrimaryBouncerCallbackInteractor @Inject constructor() {
private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
- private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>()
+ private var expansionCallbacks = ArrayList<KeyguardBouncer.PrimaryBouncerExpansionCallback>()
/** Add a KeyguardResetCallback. */
fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
resetCallbacks.addIfAbsent(callback)
@@ -38,7 +38,7 @@
}
/** Adds a callback to listen to bouncer expansion updates. */
- fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
+ fun addBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
if (!expansionCallbacks.contains(callback)) {
expansionCallbacks.add(callback)
}
@@ -48,7 +48,7 @@
* Removes a previously added callback. If the callback was never added, this method does
* nothing.
*/
- fun removeBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
+ fun removeBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
expansionCallbacks.remove(callback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index dbb0352..3b31dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -44,24 +44,27 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-/** Encapsulates business logic for interacting with the lock-screen bouncer. */
+/**
+ * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
+ * bouncer.
+ */
@SysUISingleton
-class BouncerInteractor
+class PrimaryBouncerInteractor
@Inject
constructor(
private val repository: KeyguardBouncerRepository,
- private val bouncerView: BouncerView,
+ private val primaryBouncerView: BouncerView,
@Main private val mainHandler: Handler,
private val keyguardStateController: KeyguardStateController,
private val keyguardSecurityModel: KeyguardSecurityModel,
- private val callbackInteractor: BouncerCallbackInteractor,
+ private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
private val falsingCollector: FalsingCollector,
private val dismissCallbackRegistry: DismissCallbackRegistry,
keyguardBypassController: KeyguardBypassController,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
/** Whether we want to wait for face auth. */
- private val bouncerFaceDelay =
+ private val primaryBouncerFaceDelay =
keyguardStateController.isFaceAuthEnabled &&
!keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
KeyguardUpdateMonitor.getCurrentUser()
@@ -70,38 +73,39 @@
!keyguardUpdateMonitor.userNeedsStrongAuth() &&
!keyguardBypassController.bypassEnabled
- /** Runnable to show the bouncer. */
+ /** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
- repository.setVisible(true)
- repository.setShow(
+ repository.setPrimaryVisible(true)
+ repository.setPrimaryShow(
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
errorMessage = repository.bouncerErrorMessage,
expansionAmount = repository.panelExpansionAmount.value
)
)
- repository.setShowingSoon(false)
+ repository.setPrimaryShowingSoon(false)
}
val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
- val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull()
- val hide: Flow<Unit> = repository.hide.filter { it }.map {}
- val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
- val isVisible: Flow<Boolean> = repository.isVisible
+ val show: Flow<KeyguardBouncerModel> = repository.primaryBouncerShow.filterNotNull()
+ val hide: Flow<Unit> = repository.primaryBouncerHide.filter { it }.map {}
+ val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
+ val isVisible: Flow<Boolean> = repository.primaryBouncerVisible
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
- repository.startingDisappearAnimation.filterNotNull()
+ repository.primaryBouncerStartingDisappearAnimation.filterNotNull()
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition
val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
/** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
- val bouncerExpansion: Flow<Float> = //
- combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
- ->
- if (isVisible) {
- 1f - expansionAmount
+ val bouncerExpansion: Flow<Float> =
+ combine(repository.panelExpansionAmount, repository.primaryBouncerVisible) {
+ panelExpansion,
+ primaryBouncerVisible ->
+ if (primaryBouncerVisible) {
+ 1f - panelExpansion
} else {
0f
}
@@ -113,16 +117,16 @@
@JvmOverloads
fun show(isScrimmed: Boolean) {
// Reset some states as we show the bouncer.
- repository.setShowMessage(null)
repository.setOnScreenTurnedOff(false)
repository.setKeyguardAuthenticated(null)
- repository.setHide(false)
- repository.setStartingToHide(false)
+ repository.setPrimaryHide(false)
+ repository.setPrimaryStartingToHide(false)
val resumeBouncer =
- (repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer()
+ (repository.primaryBouncerVisible.value ||
+ repository.primaryBouncerShowingSoon.value) && needsFullscreenBouncer()
- if (!resumeBouncer && repository.show.value != null) {
+ if (!resumeBouncer && repository.primaryBouncerShow.value != null) {
// If bouncer is visible, the bouncer is already showing.
return
}
@@ -134,29 +138,29 @@
}
Trace.beginSection("KeyguardBouncer#show")
- repository.setScrimmed(isScrimmed)
+ repository.setPrimaryScrimmed(isScrimmed)
if (isScrimmed) {
setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
}
if (resumeBouncer) {
- bouncerView.delegate?.resume()
+ primaryBouncerView.delegate?.resume()
// Bouncer is showing the next security screen and we just need to prompt a resume.
return
}
- if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+ if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
// Keyguard is done.
return
}
- repository.setShowingSoon(true)
- if (bouncerFaceDelay) {
+ repository.setPrimaryShowingSoon(true)
+ if (primaryBouncerFaceDelay) {
mainHandler.postDelayed(showRunnable, 1200L)
} else {
DejankUtils.postAfterTraversal(showRunnable)
}
keyguardStateController.notifyBouncerShowing(true)
- callbackInteractor.dispatchStartingToShow()
+ primaryBouncerCallbackInteractor.dispatchStartingToShow()
Trace.endSection()
}
@@ -174,10 +178,10 @@
falsingCollector.onBouncerHidden()
keyguardStateController.notifyBouncerShowing(false /* showing */)
cancelShowRunnable()
- repository.setShowingSoon(false)
- repository.setVisible(false)
- repository.setHide(true)
- repository.setShow(null)
+ repository.setPrimaryShowingSoon(false)
+ repository.setPrimaryVisible(false)
+ repository.setPrimaryHide(true)
+ repository.setPrimaryShow(null)
Trace.endSection()
}
@@ -191,7 +195,7 @@
fun setPanelExpansion(expansion: Float) {
val oldExpansion = repository.panelExpansionAmount.value
val expansionChanged = oldExpansion != expansion
- if (repository.startingDisappearAnimation.value == null) {
+ if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
repository.setPanelExpansion(expansion)
}
@@ -200,25 +204,28 @@
oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE
) {
falsingCollector.onBouncerShown()
- callbackInteractor.dispatchFullyShown()
+ primaryBouncerCallbackInteractor.dispatchFullyShown()
} else if (
expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
) {
- repository.setVisible(false)
- repository.setShow(null)
- falsingCollector.onBouncerHidden()
- DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() }
- callbackInteractor.dispatchFullyHidden()
+ /*
+ * There are cases where #hide() was not invoked, such as when
+ * NotificationPanelViewController controls the hide animation. Make sure the state gets
+ * updated by calling #hide() directly.
+ */
+ hide()
+ DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
+ primaryBouncerCallbackInteractor.dispatchFullyHidden()
} else if (
expansion != KeyguardBouncer.EXPANSION_VISIBLE &&
oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE
) {
- callbackInteractor.dispatchStartingToHide()
- repository.setStartingToHide(true)
+ primaryBouncerCallbackInteractor.dispatchStartingToHide()
+ repository.setPrimaryStartingToHide(true)
}
if (expansionChanged) {
- callbackInteractor.dispatchExpansionChanged(expansion)
+ primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion)
}
}
@@ -236,7 +243,7 @@
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
- bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
+ primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
/** Update the resources of the views. */
@@ -264,9 +271,14 @@
repository.setKeyguardAuthenticated(null)
}
+ /** Notifies that the message was shown. */
+ fun onMessageShown() {
+ repository.setShowMessage(null)
+ }
+
/** Notify that view visibility has changed. */
fun notifyBouncerVisibilityHasChanged(visibility: Int) {
- callbackInteractor.dispatchVisibilityChanged(visibility)
+ primaryBouncerCallbackInteractor.dispatchVisibilityChanged(visibility)
}
/** Notify that the resources have been updated */
@@ -283,38 +295,39 @@
fun startDisappearAnimation(runnable: Runnable) {
val finishRunnable = Runnable {
runnable.run()
- repository.setStartDisappearAnimation(null)
+ repository.setPrimaryStartDisappearAnimation(null)
}
- repository.setStartDisappearAnimation(finishRunnable)
+ repository.setPrimaryStartDisappearAnimation(finishRunnable)
}
/** Returns whether bouncer is fully showing. */
fun isFullyShowing(): Boolean {
- return (repository.showingSoon.value || repository.isVisible.value) &&
+ return (repository.primaryBouncerShowingSoon.value ||
+ repository.primaryBouncerVisible.value) &&
repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
- repository.startingDisappearAnimation.value == null
+ repository.primaryBouncerStartingDisappearAnimation.value == null
}
/** Returns whether bouncer is scrimmed. */
fun isScrimmed(): Boolean {
- return repository.isScrimmed.value
+ return repository.primaryBouncerScrimmed.value
}
/** If bouncer expansion is between 0f and 1f non-inclusive. */
fun isInTransit(): Boolean {
- return repository.showingSoon.value ||
+ return repository.primaryBouncerShowingSoon.value ||
repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
}
/** Return whether bouncer is animating away. */
fun isAnimatingAway(): Boolean {
- return repository.startingDisappearAnimation.value != null
+ return repository.primaryBouncerStartingDisappearAnimation.value != null
}
/** Return whether bouncer will dismiss with actions */
fun willDismissWithAction(): Boolean {
- return bouncerView.delegate?.willDismissWithActions() == true
+ return primaryBouncerView.delegate?.willDismissWithActions() == true
}
/** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 37f33af..dbffeab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -46,5 +46,19 @@
@Binds
@IntoSet
+ abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
+
+ @Binds
+ @IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun dreamingLockscreen(
+ impl: DreamingLockscreenTransitionInteractor
+ ): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
new file mode 100644
index 0000000..7039188
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.keyguard.shared.model
+
+/** Model device doze states. */
+enum class DozeStateModel {
+ /** Default state. Transition to INITIALIZED to get Doze going. */
+ UNINITIALIZED,
+ /** Doze components are set up. Followed by transition to DOZE or DOZE_AOD. */
+ INITIALIZED,
+ /** Regular doze. Device is asleep and listening for pulse triggers. */
+ DOZE,
+ /** Deep doze. Device is asleep and is not listening for pulse triggers. */
+ DOZE_SUSPEND_TRIGGERS,
+ /** Always-on doze. Device is asleep, showing UI and listening for pulse triggers. */
+ DOZE_AOD,
+ /** Pulse has been requested. Device is awake and preparing UI */
+ DOZE_REQUEST_PULSE,
+ /** Pulse is showing. Device is awake and showing UI. */
+ DOZE_PULSING,
+ /** Pulse is showing with bright wallpaper. Device is awake and showing UI. */
+ DOZE_PULSING_BRIGHT,
+ /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */
+ DOZE_PULSE_DONE,
+ /** Doze is done. DozeService is finished. */
+ FINISH,
+ /** AOD, but the display is temporarily off. */
+ DOZE_AOD_PAUSED,
+ /** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */
+ DOZE_AOD_PAUSING,
+ /** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */
+ DOZE_AOD_DOCKED
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
similarity index 67%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
index 817c209f..e96ace2 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
@@ -11,11 +11,12 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.keyguard.shared.model
-package com.android.settingslib.spa.gallery
-
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
+/** Doze transition information. */
+data class DozeTransitionModel(
+ val from: DozeStateModel = DozeStateModel.UNINITIALIZED,
+ val to: DozeStateModel = DozeStateModel.UNINITIALIZED,
+)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
similarity index 73%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
index 817c209f..a7a5957 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
@@ -12,10 +12,13 @@
* WITHOUT 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.spa.gallery
+package com.android.systemui.keyguard.shared.model
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
+/** Represents a flag that's consumed by the settings or wallpaper picker app. */
+data class KeyguardPickerFlag(
+ val name: String,
+ val value: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 7958033..dd908c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -17,12 +17,29 @@
/** List of all possible states to transition to/from */
enum class KeyguardState {
- /**
- * For initialization as well as when the security method is set to NONE, indicating that
- * the keyguard should never be shown.
+ /*
+ * The display is completely off, as well as any sensors that would trigger the device to wake
+ * up.
*/
- NONE,
- /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+ OFF,
+ /**
+ * The device has entered a special low-power mode within SystemUI. Doze is technically a
+ * special dream service implementation. No UI is visible. In this state, a least some
+ * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for
+ * notifications is enabled, allowing the device to quickly wake up.
+ */
+ DOZING,
+ /*
+ * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
+ * DOZING is an example of special version of this state. Dreams may be implemented by third
+ * parties to present their own UI over keyguard, like a screensaver.
+ */
+ DREAMING,
+ /**
+ * The device has entered a special low-power mode within SystemUI, also called the Always-on
+ * Display (AOD). A minimal UI is presented to show critical information. If the device is in
+ * low-power mode without a UI, then it is DOZING.
+ */
AOD,
/*
* The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
@@ -34,7 +51,6 @@
* unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
*/
LOCKSCREEN,
-
/*
* Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
* is being removed, but there are other cases where the user is swiping away keyguard, such as
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
index 0e0465b..38a93b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -17,7 +17,12 @@
/** Possible states for a running transition between [State] */
enum class TransitionState {
+ /* Transition has begun. */
STARTED,
+ /* Transition is actively running. */
RUNNING,
- FINISHED
+ /* Transition has completed successfully. */
+ FINISHED,
+ /* Transition has been interrupted, and not completed successfully. */
+ CANCELED,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 732a6f7..767fd58 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -17,8 +17,8 @@
/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
data class TransitionStep(
- val from: KeyguardState = KeyguardState.NONE,
- val to: KeyguardState = KeyguardState.NONE,
+ val from: KeyguardState = KeyguardState.OFF,
+ val to: KeyguardState = KeyguardState.OFF,
val value: Float = 0f, // constrained [0.0, 1.0]
val transitionState: TransitionState = TransitionState.FINISHED,
val ownerName: String = "",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 64f834d..92040f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -24,5 +24,15 @@
/** Device is now fully awake and interactive. */
AWAKE,
/** Signal that the device is now going to sleep. */
- STARTING_TO_SLEEP,
+ STARTING_TO_SLEEP;
+
+ companion object {
+ fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
+ return model == ASLEEP || model == STARTING_TO_SLEEP
+ }
+
+ fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
+ return model == AWAKE || model == STARTING_TO_WAKE
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 2c99ca5..3276b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -27,6 +27,8 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.LockIconViewController
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.Expandable
@@ -69,6 +71,11 @@
/** Notifies that device configuration has changed. */
fun onConfigurationChanged()
+
+ /**
+ * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+ */
+ fun shouldConstrainToTopOfLockIcon(): Boolean
}
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@@ -208,6 +215,9 @@
override fun onConfigurationChanged() {
configurationBasedDimensions.value = loadFromResources(view)
}
+
+ override fun shouldConstrainToTopOfLockIcon(): Boolean =
+ viewModel.shouldConstrainToTopOfLockIcon()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index a22958b..3c927ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -32,7 +32,6 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@@ -94,6 +93,10 @@
viewModel.setBouncerViewDelegate(delegate)
launch {
viewModel.show.collect {
+ hostViewController.showPromptReason(it.promptReason)
+ it.errorMessage?.let { errorMessage ->
+ hostViewController.showErrorMessage(errorMessage)
+ }
hostViewController.showPrimarySecurityScreen()
hostViewController.appear(
SystemBarUtils.getStatusBarHeight(view.context)
@@ -102,18 +105,6 @@
}
launch {
- viewModel.showPromptReason.collect { prompt ->
- hostViewController.showPromptReason(prompt)
- }
- }
-
- launch {
- viewModel.showBouncerErrorMessage.collect { errorMessage ->
- hostViewController.showErrorMessage(errorMessage)
- }
- }
-
- launch {
viewModel.showWithFullExpansion.collect { model ->
hostViewController.resetSecurityContainer()
hostViewController.showPromptReason(model.promptReason)
@@ -190,6 +181,7 @@
launch {
viewModel.bouncerShowMessage.collect {
hostViewController.showMessage(it.message, it.colorStateList)
+ viewModel.onMessageShown()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index b6b2304..227796f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -90,6 +90,12 @@
.distinctUntilChanged()
}
+ /**
+ * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+ */
+ fun shouldConstrainToTopOfLockIcon(): Boolean =
+ bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
+
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9a92843..503c8ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -19,14 +19,13 @@
import android.view.View
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
/** Models UI state for the lock screen bouncer; handles user input. */
@@ -34,7 +33,7 @@
@Inject
constructor(
private val view: BouncerView,
- private val interactor: BouncerInteractor,
+ private val interactor: PrimaryBouncerInteractor,
) {
/** Observe on bouncer expansion amount. */
val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
@@ -45,13 +44,6 @@
/** Observe whether bouncer is showing. */
val show: Flow<KeyguardBouncerModel> = interactor.show
- /** Observe bouncer prompt when bouncer is showing. */
- val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason }
-
- /** Observe bouncer error message when bouncer is showing. */
- val showBouncerErrorMessage: Flow<CharSequence> =
- interactor.show.map { it.errorMessage }.filterNotNull()
-
/** Observe visible expansion when bouncer is showing. */
val showWithFullExpansion: Flow<KeyguardBouncerModel> =
interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
@@ -94,6 +86,11 @@
interactor.notifyKeyguardAuthenticatedHandled()
}
+ /** Notifies that the message was shown. */
+ fun onMessageShown() {
+ interactor.onMessageShown()
+ }
+
/** Observe whether back button is enabled. */
fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
return interactor.isBackButtonEnabled.map { enabled ->
diff --git a/core/java/com/android/internal/app/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
similarity index 88%
rename from core/java/com/android/internal/app/LogAccessDialogActivity.java
rename to packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index 4adb867..a88a4ca 100644
--- a/core/java/com/android/internal/app/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.systemui.logcat;
import android.annotation.StyleRes;
import android.app.Activity;
@@ -23,7 +23,6 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -43,7 +42,9 @@
import android.widget.Button;
import android.widget.TextView;
-import com.android.internal.R;
+import com.android.internal.app.ILogAccessDialogCallback;
+import com.android.systemui.R;
+
/**
* Dialog responsible for obtaining user consent per-use log access
@@ -93,10 +94,7 @@
mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
// create View
- boolean isDarkTheme = (getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
- int themeId = isDarkTheme ? android.R.style.Theme_DeviceDefault_Dialog_Alert :
- android.R.style.Theme_DeviceDefault_Light_Dialog_Alert;
+ int themeId = R.style.LogAccessDialogTheme;
mAlertView = createView(themeId);
// create AlertDialog
@@ -177,8 +175,7 @@
PackageManager.MATCH_DIRECT_BOOT_AUTO,
UserHandle.getUserId(uid)).loadLabel(pm);
- String titleString = context.getString(
- com.android.internal.R.string.log_access_confirmation_title, appLabel);
+ String titleString = context.getString(R.string.log_access_confirmation_title, appLabel);
return titleString;
}
@@ -235,15 +232,12 @@
@Override
public void onClick(View view) {
try {
- switch (view.getId()) {
- case R.id.log_access_dialog_allow_button:
- mCallback.approveAccessForClient(mUid, mPackageName);
- finish();
- break;
- case R.id.log_access_dialog_deny_button:
- declineLogAccess();
- finish();
- break;
+ if (view.getId() == R.id.log_access_dialog_allow_button) {
+ mCallback.approveAccessForClient(mUid, mPackageName);
+ finish();
+ } else if (view.getId() == R.id.log_access_dialog_allow_button) {
+ declineLogAccess();
+ finish();
}
} catch (RemoteException e) {
finish();
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/OWNERS b/packages/SystemUI/src/com/android/systemui/logcat/OWNERS
new file mode 100644
index 0000000..9c0c414
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/logcat/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1218649
+file:platform/frameworks/base:/services/core/java/com/android/server/logcat/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 397bffc..22f91f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -18,6 +18,9 @@
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
+import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
@@ -44,6 +47,8 @@
import com.android.systemui.R;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
+import com.android.systemui.screenrecord.ScreenShareOption;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.Utils;
@@ -102,7 +107,9 @@
CharSequence dialogText = null;
CharSequence dialogTitle = null;
+ String appName = null;
if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
+ // TODO(b/253438807): handle special app name
dialogText = getString(R.string.media_projection_dialog_service_text);
dialogTitle = getString(R.string.media_projection_dialog_service_title);
} else {
@@ -132,7 +139,7 @@
String unsanitizedAppName = TextUtils.ellipsize(label,
paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
- String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
+ appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
String actionText = getString(R.string.media_projection_dialog_text, appName);
SpannableString message = new SpannableString(actionText);
@@ -146,27 +153,28 @@
dialogTitle = getString(R.string.media_projection_dialog_title, appName);
}
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
- R.style.Theme_SystemUI_Dialog)
- .setTitle(dialogTitle)
- .setIcon(R.drawable.ic_media_projection_permission)
- .setMessage(dialogText)
- .setPositiveButton(R.string.media_projection_action_text, this)
- .setNeutralButton(android.R.string.cancel, this)
- .setOnCancelListener(this);
-
if (isPartialScreenSharingEnabled()) {
- // This is a temporary entry point before we have a new permission dialog
- // TODO(b/233183090): this activity should be redesigned to have a dropdown selector
- dialogBuilder.setNegativeButton("App", this);
+ mDialog = new MediaProjectionPermissionDialog(this, () -> {
+ ScreenShareOption selectedOption =
+ ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
+ grantMediaProjectionPermission(selectedOption.getMode());
+ }, appName);
+ } else {
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
+ R.style.Theme_SystemUI_Dialog)
+ .setTitle(dialogTitle)
+ .setIcon(R.drawable.ic_media_projection_permission)
+ .setMessage(dialogText)
+ .setPositiveButton(R.string.media_projection_action_text, this)
+ .setNeutralButton(android.R.string.cancel, this);
+ mDialog = dialogBuilder.create();
}
- mDialog = dialogBuilder.create();
-
SystemUIDialog.registerDismissListener(mDialog);
SystemUIDialog.applyFlags(mDialog);
SystemUIDialog.setDialogSize(mDialog);
+ mDialog.setOnCancelListener(this);
mDialog.create();
mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
@@ -186,12 +194,17 @@
@Override
public void onClick(DialogInterface dialog, int which) {
+ if (which == AlertDialog.BUTTON_POSITIVE) {
+ grantMediaProjectionPermission(ENTIRE_SCREEN);
+ }
+ }
+
+ private void grantMediaProjectionPermission(int screenShareMode) {
try {
- if (which == AlertDialog.BUTTON_POSITIVE) {
+ if (screenShareMode == ENTIRE_SCREEN) {
setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
}
-
- if (isPartialScreenSharingEnabled() && which == AlertDialog.BUTTON_NEGATIVE) {
+ if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
IMediaProjection projection = createProjection(mUid, mPackageName);
final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
index a7f1b95..a8f39fa9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
@@ -26,7 +26,8 @@
import androidx.constraintlayout.widget.Barrier
import com.android.systemui.R
import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "MediaViewHolder"
@@ -38,6 +39,8 @@
// Player information
val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
+ val turbulenceNoiseView =
+ itemView.requireViewById<TurbulenceNoiseView>(R.id.turbulence_noise_view)
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
index a7ed69a..cacb3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
@@ -29,7 +29,6 @@
private val smartspaceMediaTargetListeners: MutableList<SmartspaceTargetListener> =
mutableListOf()
- private var smartspaceMediaTargets: List<SmartspaceTarget> = listOf()
override fun registerListener(smartspaceTargetListener: SmartspaceTargetListener) {
smartspaceMediaTargetListeners.add(smartspaceTargetListener)
@@ -41,22 +40,7 @@
/** Updates Smartspace data and propagates it to any listeners. */
override fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
- // Filter out non-media targets.
- val mediaTargets = mutableListOf<SmartspaceTarget>()
- for (target in targets) {
- val smartspaceTarget = target
- if (smartspaceTarget.featureType == SmartspaceTarget.FEATURE_MEDIA) {
- mediaTargets.add(smartspaceTarget)
- }
- }
-
- if (!mediaTargets.isEmpty()) {
- Log.d(TAG, "Forwarding Smartspace media updates $mediaTargets")
- }
-
- smartspaceMediaTargets = mediaTargets
- smartspaceMediaTargetListeners.forEach {
- it.onSmartspaceTargetsUpdated(smartspaceMediaTargets)
- }
+ Log.d(TAG, "Forwarding Smartspace updates $targets")
+ smartspaceMediaTargetListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 45b319b..cf71d67 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -20,13 +20,12 @@
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.time.SystemClock
import java.util.SortedMap
@@ -62,14 +61,13 @@
@Inject
constructor(
private val context: Context,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val broadcastSender: BroadcastSender,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@Main private val executor: Executor,
private val systemClock: SystemClock,
private val logger: MediaUiEventLogger
) : MediaDataManager.Listener {
- private val userTracker: CurrentUserTracker
private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
internal val listeners: Set<MediaDataManager.Listener>
get() = _listeners.toSet()
@@ -81,15 +79,15 @@
private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
private var reactivatedKey: String? = null
- init {
- userTracker =
- object : CurrentUserTracker(broadcastDispatcher) {
- override fun onUserSwitched(newUserId: Int) {
- // Post this so we can be sure lockscreenUserManager already got the broadcast
- executor.execute { handleUserSwitched(newUserId) }
- }
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ handleUserSwitched(newUser)
}
- userTracker.startTracking()
+ }
+
+ init {
+ userTracker.addCallback(userTrackerCallback, executor)
}
override fun onMediaDataLoaded(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 14dd990..3012bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -82,7 +82,6 @@
import java.io.IOException
import java.io.PrintWriter
import java.util.concurrent.Executor
-import java.util.concurrent.Executors
import javax.inject.Inject
// URI fields to try loading album art from
@@ -154,6 +153,7 @@
class MediaDataManager(
private val context: Context,
@Background private val backgroundExecutor: Executor,
+ @Main private val uiExecutor: Executor,
@Main private val foregroundExecutor: DelayableExecutor,
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -171,7 +171,8 @@
private val systemClock: SystemClock,
private val tunerService: TunerService,
private val mediaFlags: MediaFlags,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
+ private val smartspaceManager: SmartspaceManager,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -218,6 +219,7 @@
constructor(
context: Context,
@Background backgroundExecutor: Executor,
+ @Main uiExecutor: Executor,
@Main foregroundExecutor: DelayableExecutor,
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
@@ -233,10 +235,12 @@
clock: SystemClock,
tunerService: TunerService,
mediaFlags: MediaFlags,
- logger: MediaUiEventLogger
+ logger: MediaUiEventLogger,
+ smartspaceManager: SmartspaceManager,
) : this(
context,
backgroundExecutor,
+ uiExecutor,
foregroundExecutor,
mediaControllerFactory,
broadcastDispatcher,
@@ -254,7 +258,8 @@
clock,
tunerService,
mediaFlags,
- logger
+ logger,
+ smartspaceManager,
)
private val appChangeReceiver =
@@ -314,21 +319,18 @@
// Register for Smartspace data updates.
smartspaceMediaDataProvider.registerListener(this)
- val smartspaceManager: SmartspaceManager =
- context.getSystemService(SmartspaceManager::class.java)
smartspaceSession =
smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
)
smartspaceSession?.let {
it.addOnTargetsAvailableListener(
- // Use a new thread listening to Smartspace updates instead of using the existing
- // backgroundExecutor. SmartspaceSession has scheduled routine updates which can be
- // unpredictable on test simulators, using the backgroundExecutor makes it's hard to
- // test the threads numbers.
- // Switch to use backgroundExecutor when SmartspaceSession has a good way to be
- // mocked.
- Executors.newCachedThreadPool(),
+ // Use a main uiExecutor thread listening to Smartspace updates instead of using
+ // the existing background executor.
+ // SmartspaceSession has scheduled routine updates which can be unpredictable on
+ // test simulators, using the backgroundExecutor makes it's hard to test the threads
+ // numbers.
+ uiExecutor,
SmartspaceSession.OnTargetsAvailableListener { targets ->
smartspaceMediaDataProvider.onTargetsAvailable(targets)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 918417f..93be6a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -29,7 +29,8 @@
import com.android.settingslib.Utils
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
/**
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -102,13 +103,21 @@
private val context: Context,
private val mediaViewHolder: MediaViewHolder,
private val multiRippleController: MultiRippleController,
+ private val turbulenceNoiseController: TurbulenceNoiseController,
animatingColorTransitionFactory: AnimatingColorTransitionFactory
) {
constructor(
context: Context,
mediaViewHolder: MediaViewHolder,
multiRippleController: MultiRippleController,
- ) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition)
+ turbulenceNoiseController: TurbulenceNoiseController
+ ) : this(
+ context,
+ mediaViewHolder,
+ multiRippleController,
+ turbulenceNoiseController,
+ ::AnimatingColorTransition
+ )
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
val surfaceColor =
@@ -129,6 +138,7 @@
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
multiRippleController.updateColor(accentPrimary)
+ turbulenceNoiseController.updateNoiseColor(accentPrimary)
}
val accentSecondary =
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 baeee9f..8aaee81 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
@@ -610,7 +610,8 @@
// are
// elements in mediaPlayers.
if (MediaPlayerData.players().size != mediaContent.childCount) {
- throw IllegalStateException(
+ Log.e(
+ TAG,
"Size of players list and number of views in carousel are out of sync. " +
"Players size is ${MediaPlayerData.players().size}. " +
"View count is ${mediaContent.childCount}."
@@ -671,7 +672,8 @@
// are
// elements in mediaPlayers.
if (MediaPlayerData.players().size != mediaContent.childCount) {
- throw IllegalStateException(
+ Log.e(
+ TAG,
"Size of players list and number of views in carousel are out of sync. " +
"Players size is ${MediaPlayerData.players().size}. " +
"View count is ${mediaContent.childCount}."
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 5b14cf3..21e64e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -31,6 +31,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
@@ -64,6 +65,7 @@
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.settingslib.widget.AdaptiveIcon;
@@ -97,13 +99,16 @@
import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.ripple.MultiRippleController;
-import com.android.systemui.ripple.RippleAnimation;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
@@ -216,7 +221,9 @@
private boolean mShowBroadcastDialogButton = false;
private String mSwitchBroadcastApp;
private MultiRippleController mMultiRippleController;
+ private TurbulenceNoiseController mTurbulenceNoiseController;
private FeatureFlags mFeatureFlags;
+ private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
/**
* Initialize a new control panel
@@ -373,6 +380,7 @@
mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
vh.getPlayer().setOnLongClickListener(v -> {
+ if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
if (!mMediaViewController.isGutsVisible()) {
openGuts();
return true;
@@ -393,9 +401,20 @@
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
- mMultiRippleController = new MultiRippleController(vh.getMultiRippleView());
+ MultiRippleView multiRippleView = vh.getMultiRippleView();
+ mMultiRippleController = new MultiRippleController(multiRippleView);
+ mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
+ multiRippleView.addRipplesFinishedListener(
+ () -> {
+ if (mTurbulenceNoiseAnimationConfig == null) {
+ mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
+ }
+ // Color will be correctly updated in ColorSchemeTransition.
+ mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+ }
+ );
mColorSchemeTransition = new ColorSchemeTransition(
- mContext, mMediaViewHolder, mMultiRippleController);
+ mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
}
@@ -423,6 +442,7 @@
mMediaViewController.attach(recommendations, MediaViewController.TYPE.RECOMMENDATION);
mRecommendationViewHolder.getRecommendations().setOnLongClickListener(v -> {
+ if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
if (!mMediaViewController.isGutsVisible()) {
openGuts();
return true;
@@ -569,7 +589,10 @@
seamlessView.setContentDescription(deviceString);
seamlessView.setOnClickListener(
v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (mFalsingManager.isFalseTap(
+ mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY :
+ FalsingManager.LOW_PENALTY)) {
return;
}
@@ -992,7 +1015,10 @@
} else {
button.setEnabled(true);
button.setOnClickListener(v -> {
- if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (!mFalsingManager.isFalseTap(
+ mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY :
+ FalsingManager.LOW_PENALTY)) {
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
action.run();
@@ -1025,7 +1051,7 @@
/* maxWidth= */ maxSize,
/* maxHeight= */ maxSize,
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
- mColorSchemeTransition.getAccentPrimary().getTargetColor(),
+ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* opacity= */ 100,
/* shouldFillRipple= */ false,
/* sparkleStrength= */ 0f,
@@ -1034,6 +1060,26 @@
);
}
+ private TurbulenceNoiseAnimationConfig createLingeringNoiseAnimation() {
+ return new TurbulenceNoiseAnimationConfig(
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+ /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedY= */ 0f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+ /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
+ // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
+ // Thus, set the background color with alpha 0.
+ /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
+ TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
+ /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_DURATION_IN_MILLIS,
+ this.getContext().getResources().getDisplayMetrics().density,
+ BlendMode.PLUS,
+ /* onAnimationEnd= */ null
+ );
+ }
private void clearButton(final ImageButton button) {
button.setImageDrawable(null);
button.setContentDescription(null);
@@ -1191,6 +1237,7 @@
setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, itemIndex);
// Bubble up the long-click event to the card.
mediaCoverContainer.setOnLongClickListener(v -> {
+ if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
View parent = (View) v.getParent();
if (parent != null) {
parent.performLongClick();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index dd9d35b..55fce59 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -35,25 +35,40 @@
private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
- if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG,
- intent.action)) {
- val packageName: String? =
- intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
- if (!TextUtils.isEmpty(packageName)) {
- mediaOutputDialogFactory.create(packageName!!, false)
- } else if (DEBUG) {
- Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
+ when {
+ TextUtils.equals(Intent.ACTION_SHOW_OUTPUT_SWITCHER, intent.action) -> {
+ val packageName: String? = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
+ launchMediaOutputDialogIfPossible(packageName)
}
- } else if (TextUtils.equals(
- MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG,
- intent.action)) {
- val packageName: String? =
+ TextUtils.equals(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action) -> {
+ val packageName: String? =
intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
- if (!TextUtils.isEmpty(packageName)) {
- mediaOutputBroadcastDialogFactory.create(packageName!!, false)
- } else if (DEBUG) {
- Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
+ launchMediaOutputDialogIfPossible(packageName)
}
+ TextUtils.equals(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG,
+ intent.action) -> {
+ val packageName: String? =
+ intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
+ launchMediaOutputBroadcastDialogIfPossible(packageName)
+ }
+ }
+ }
+
+ private fun launchMediaOutputDialogIfPossible(packageName: String?) {
+ if (!packageName.isNullOrEmpty()) {
+ mediaOutputDialogFactory.create(packageName, false)
+ } else if (DEBUG) {
+ Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
+ }
+ }
+
+ private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
+ if (!packageName.isNullOrEmpty()) {
+ mediaOutputBroadcastDialogFactory.create(packageName, false)
+ } else if (DEBUG) {
+ Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index a4a96806..647beb9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -61,7 +61,7 @@
@SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
as StatusBarManager
- val routeInfo = MediaRoute2Info.Builder("id", args[0])
+ val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0])
.addFeature("feature")
val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
if (useAppIcon) {
@@ -107,7 +107,7 @@
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " +
- "<deviceName> <chipState> useAppIcon=[true|false]")
+ "<deviceName> <chipState> useAppIcon=[true|false] <id>")
}
}
@@ -127,8 +127,10 @@
@SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE
val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
as StatusBarManager
- val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
- .addFeature("feature")
+ val routeInfo = MediaRoute2Info.Builder(
+ if (args.size >= 3) args[2] else "id",
+ "Test Name"
+ ).addFeature("feature")
val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
if (useAppIcon) {
routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
@@ -144,7 +146,7 @@
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $RECEIVER_COMMAND " +
- "<chipState> useAppIcon=[true|false]")
+ "<chipState> useAppIcon=[true|false] <id>")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 8bddffc..cc5e256 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -56,7 +56,7 @@
* TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
*/
@SysUISingleton
-class MediaTttChipControllerReceiver @Inject constructor(
+open class MediaTttChipControllerReceiver @Inject constructor(
private val commandQueue: CommandQueue,
context: Context,
@MediaTttReceiverLogger logger: MediaTttLogger,
@@ -121,18 +121,32 @@
uiEventLogger.logReceiverStateChange(chipState)
if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
- removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
+ removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
return
}
if (appIcon == null) {
- displayView(ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appName))
+ displayView(
+ ChipReceiverInfo(
+ routeInfo,
+ appIconDrawableOverride = null,
+ appName,
+ id = routeInfo.id,
+ )
+ )
return
}
appIcon.loadDrawableAsync(
context,
Icon.OnDrawableLoadedListener { drawable ->
- displayView(ChipReceiverInfo(routeInfo, drawable, appName))
+ displayView(
+ ChipReceiverInfo(
+ routeInfo,
+ drawable,
+ appName,
+ id = routeInfo.id,
+ )
+ )
},
// Notify the listener on the main handler since the listener will update
// the UI.
@@ -169,15 +183,28 @@
val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(-1 * getTranslationAmount().toFloat())
- .setDuration(30.frames)
+ .setDuration(ICON_TRANSLATION_ANIM_DURATION)
.start()
appIconView.animate()
.alpha(1f)
- .setDuration(5.frames)
+ .setDuration(ICON_ALPHA_ANIM_DURATION)
.start()
// Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
appIconView.postOnAnimation { view.requestAccessibilityFocus() }
- startRipple(view.requireViewById(R.id.ripple))
+ expandRipple(view.requireViewById(R.id.ripple))
+ }
+
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ val appIconView = view.getAppIconView()
+ appIconView.animate()
+ .translationYBy(getTranslationAmount().toFloat())
+ .setDuration(ICON_TRANSLATION_ANIM_DURATION)
+ .start()
+ appIconView.animate()
+ .alpha(0f)
+ .setDuration(ICON_ALPHA_ANIM_DURATION)
+ .start()
+ (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -191,11 +218,22 @@
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
}
- private fun startRipple(rippleView: ReceiverChipRippleView) {
+ private fun expandRipple(rippleView: ReceiverChipRippleView) {
if (rippleView.rippleInProgress()) {
// Skip if ripple is still playing
return
}
+
+ // In case the device orientation changes, we need to reset the layout.
+ rippleView.addOnLayoutChangeListener (
+ View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+ if (v == null) return@OnLayoutChangeListener
+
+ val layoutChangedRippleView = v as ReceiverChipRippleView
+ layoutRipple(layoutChangedRippleView)
+ layoutChangedRippleView.invalidate()
+ }
+ )
rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(view: View?) {}
@@ -205,7 +243,7 @@
}
val attachedRippleView = view as ReceiverChipRippleView
layoutRipple(attachedRippleView)
- attachedRippleView.startRipple()
+ attachedRippleView.expandRipple()
attachedRippleView.removeOnAttachStateChangeListener(this)
}
})
@@ -228,10 +266,14 @@
}
}
+val ICON_TRANSLATION_ANIM_DURATION = 30.frames
+val ICON_ALPHA_ANIM_DURATION = 5.frames
+
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
val appNameOverride: CharSequence?,
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
+ override val id: String,
) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index e354a03..6e9fc5c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -16,19 +16,47 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.util.AttributeSet
-import com.android.systemui.ripple.RippleShader
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleView
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
*/
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
+
+ // Indicates whether the ripple started expanding.
+ private var isStarted: Boolean
+
init {
setupShader(RippleShader.RippleShape.ELLIPSE)
setRippleFill(true)
setSparkleStrength(0f)
duration = 3000L
+ isStarted = false
+ }
+
+ fun expandRipple(onAnimationEnd: Runnable? = null) {
+ isStarted = true
+ super.startRipple(onAnimationEnd)
+ }
+
+ /** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */
+ fun collapseRipple(onAnimationEnd: Runnable? = null) {
+ if (!isStarted) {
+ return // Ignore if ripple is not started yet.
+ }
+ // Reset all listeners to animator.
+ animator.removeAllListeners()
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd?.run()
+ isStarted = false
+ }
+ })
+ animator.reverse()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index d1ea2d0..bb7bc6f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -108,7 +108,7 @@
}
displayedState = null
- chipbarCoordinator.removeView(removalReason)
+ chipbarCoordinator.removeView(routeInfo.id, removalReason)
} else {
displayedState = chipState
chipbarCoordinator.displayView(
@@ -162,6 +162,7 @@
windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout,
+ id = routeInfo.id,
)
}
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 7fd100f..6c41caa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -19,6 +19,7 @@
import android.app.Activity
import android.content.ComponentName
import android.content.Context
+import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
@@ -92,6 +93,11 @@
): ConfigurationController = ConfigurationControllerImpl(activity)
@Provides
+ fun bindIconFactory(
+ context: Context
+ ): IconFactory = IconFactory.obtain(context)
+
+ @Provides
@MediaProjectionAppSelector
@MediaProjectionAppSelectorScope
fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
index 0927f3b..b85d628 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
@@ -19,13 +19,14 @@
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
-import android.content.pm.PackageManager.ComponentInfoFlags
import android.graphics.drawable.Drawable
import android.os.UserHandle
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.system.PackageManagerWrapper
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -38,14 +39,18 @@
constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val context: Context,
- private val packageManager: PackageManager
+ // Use wrapper to access hidden API that allows to get ActivityInfo for any user id
+ private val packageManagerWrapper: PackageManagerWrapper,
+ private val packageManager: PackageManager,
+ private val iconFactoryProvider: Provider<IconFactory>
) : AppIconLoader {
override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
withContext(backgroundDispatcher) {
- IconFactory.obtain(context).use<IconFactory, Drawable?> { iconFactory ->
- val activityInfo = packageManager
- .getActivityInfo(component, ComponentInfoFlags.of(0))
+ iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
+ val activityInfo =
+ packageManagerWrapper.getActivityInfo(component, userId)
+ ?: return@withContext null
val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
val userHandler = UserHandle.of(userId)
val options = IconOptions().apply { setUser(userHandler) }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index e8b49cd..7a77c47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,19 +16,19 @@
package com.android.systemui.mediaprojection.appselector.data
-import android.app.ActivityManager
import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.recents.RecentTasks
import com.android.wm.shell.util.GroupedRecentTaskInfo
import java.util.Optional
+import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-import java.util.concurrent.Executor
interface RecentTaskListProvider {
/** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
@@ -40,7 +40,8 @@
constructor(
@Background private val coroutineDispatcher: CoroutineDispatcher,
@Background private val backgroundExecutor: Executor,
- private val recentTasks: Optional<RecentTasks>
+ private val recentTasks: Optional<RecentTasks>,
+ private val userTracker: UserTracker
) : RecentTaskListProvider {
private val recents by lazy { recentTasks.getOrNull() }
@@ -67,10 +68,8 @@
getRecentTasks(
Integer.MAX_VALUE,
RECENT_IGNORE_UNAVAILABLE,
- ActivityManager.getCurrentUser(),
+ userTracker.userId,
backgroundExecutor
- ) { tasks ->
- continuation.resume(tasks)
- }
+ ) { tasks -> continuation.resume(tasks) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index b682bd1..d4991f9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -148,6 +148,7 @@
val currentRotation: Int = display.rotation
val displayWidthPx = windowMetrics.bounds.width()
+ val displayHeightPx = windowMetrics.bounds.height()
val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
val isTablet = isTablet(context)
val taskbarSize =
@@ -163,6 +164,7 @@
measuredWidth,
measuredHeight,
displayWidthPx,
+ displayHeightPx,
taskbarSize,
isTablet,
currentRotation,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 59bb2278e..2a7704f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -45,11 +45,10 @@
import com.android.systemui.shared.system.QuickStepContract;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Objects;
-public class NavigationBarInflaterView extends FrameLayout
- implements NavigationModeController.ModeChangedListener {
-
+public class NavigationBarInflaterView extends FrameLayout {
private static final String TAG = "NavBarInflater";
public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
@@ -83,6 +82,24 @@
private static final String ABSOLUTE_SUFFIX = "A";
private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C";
+ private static class Listener implements NavigationModeController.ModeChangedListener {
+ private final WeakReference<NavigationBarInflaterView> mSelf;
+
+ Listener(NavigationBarInflaterView self) {
+ mSelf = new WeakReference<>(self);
+ }
+
+ @Override
+ public void onNavigationModeChanged(int mode) {
+ NavigationBarInflaterView self = mSelf.get();
+ if (self != null) {
+ self.onNavigationModeChanged(mode);
+ }
+ }
+ }
+
+ private final Listener mListener;
+
protected LayoutInflater mLayoutInflater;
protected LayoutInflater mLandscapeInflater;
@@ -106,7 +123,8 @@
super(context, attrs);
createInflaters();
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
- mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+ mListener = new Listener(this);
+ mNavBarMode = Dependency.get(NavigationModeController.class).addListener(mListener);
}
@VisibleForTesting
@@ -146,14 +164,13 @@
return getContext().getString(defaultResource);
}
- @Override
- public void onNavigationModeChanged(int mode) {
+ private void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
}
@Override
protected void onDetachedFromWindow() {
- Dependency.get(NavigationModeController.class).removeListener(this);
+ Dependency.get(NavigationModeController.class).removeListener(mListener);
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 7964d16..26d3902 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -59,7 +59,6 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -70,9 +69,9 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -102,8 +101,8 @@
/**
* Utility class to handle edge swipes for back gesture
*/
-public class EdgeBackGestureHandler extends CurrentUserTracker
- implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
+ ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = "EdgeBackGestureHandler";
private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -172,6 +171,7 @@
private final Context mContext;
+ private final UserTracker mUserTracker;
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
private Runnable mStateChangeCallback;
@@ -321,6 +321,15 @@
private final Consumer<Boolean> mOnIsInPipStateChangedListener =
(isInPip) -> mIsInPip = isInPip;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ updateIsEnabled();
+ updateCurrentUserResources();
+ }
+ };
+
EdgeBackGestureHandler(
Context context,
OverviewProxyService overviewProxyService,
@@ -328,7 +337,7 @@
PluginManager pluginManager,
@Main Executor executor,
@Background Executor backgroundExecutor,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
@@ -340,11 +349,11 @@
Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
FeatureFlags featureFlags) {
- super(broadcastDispatcher);
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = executor;
mBackgroundExecutor = backgroundExecutor;
+ mUserTracker = userTracker;
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -463,12 +472,6 @@
}
}
- @Override
- public void onUserSwitched(int newUserId) {
- updateIsEnabled();
- updateCurrentUserResources();
- }
-
/**
* @see NavigationBarView#onAttachedToWindow()
*/
@@ -478,7 +481,7 @@
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
updateIsEnabled();
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
}
/**
@@ -490,7 +493,7 @@
mOverviewProxyService.removeCallback(mQuickSwitchListener);
mSysUiState.removeCallback(mSysUiStateCallback);
updateIsEnabled();
- stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
}
/**
@@ -1093,7 +1096,7 @@
private final PluginManager mPluginManager;
private final Executor mExecutor;
private final Executor mBackgroundExecutor;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
private final BackPanelController.Factory mBackPanelControllerFactory;
@@ -1113,7 +1116,7 @@
PluginManager pluginManager,
@Main Executor executor,
@Background Executor backgroundExecutor,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
@@ -1131,7 +1134,7 @@
mPluginManager = pluginManager;
mExecutor = executor;
mBackgroundExecutor = backgroundExecutor;
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
mBackPanelControllerFactory = backPanelControllerFactory;
@@ -1154,7 +1157,7 @@
mPluginManager,
mExecutor,
mBackgroundExecutor,
- mBroadcastDispatcher,
+ mUserTracker,
mProtoTracer,
mNavigationModeController,
mBackPanelControllerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index be82b1f..67e9664 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -1096,7 +1096,7 @@
Pair<Integer, Integer> first = emojiIndices.get(i - 1);
// Check if second emoji starts right after first starts
- if (second.first == first.second) {
+ if (Objects.equals(second.first, first.second)) {
// Check if emojis in sequence are the same
if (Objects.equals(emojiTexts.get(i), emojiTexts.get(i - 1))) {
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
index 0b565ea..e6575d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
@@ -18,7 +18,6 @@
import com.android.systemui.Dependency;
import com.android.systemui.plugins.PluginDependency.DependencyProvider;
-import com.android.systemui.shared.plugins.PluginManager;
import javax.inject.Inject;
import javax.inject.Singleton;
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
index 638f81b..146633d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -27,7 +27,6 @@
import com.android.systemui.shared.plugins.PluginActionManager;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInstance;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index dc79f40..6f645b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -26,6 +26,7 @@
import javax.inject.Inject
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
interface ChipVisibilityListener {
fun onChipVisibilityRefreshed(visible: Boolean)
@@ -54,7 +55,8 @@
private val activityStarter: ActivityStarter,
private val appOpsController: AppOpsController,
private val broadcastDispatcher: BroadcastDispatcher,
- private val safetyCenterManager: SafetyCenterManager
+ private val safetyCenterManager: SafetyCenterManager,
+ private val deviceProvisionedController: DeviceProvisionedController
) {
var chipVisibilityListener: ChipVisibilityListener? = null
@@ -134,6 +136,8 @@
fun onParentVisible() {
privacyChip.setOnClickListener {
+ // Do not expand dialog while device is not provisioned
+ if (!deviceProvisionedController.isDeviceProvisioned) return@setOnClickListener
// If the privacy chip is visible, it means there were some indicators
uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK)
if (safetyCenterEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 0697133..f92bbf7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -364,13 +364,18 @@
private void distributeTiles() {
emptyAndInflateOrRemovePages();
- final int tileCount = mPages.get(0).maxTiles();
- if (DEBUG) Log.d(TAG, "Distributing tiles");
+ final int tilesPerPageCount = mPages.get(0).maxTiles();
int index = 0;
- final int NT = mTiles.size();
- for (int i = 0; i < NT; i++) {
+ final int totalTilesCount = mTiles.size();
+ if (DEBUG) {
+ Log.d(TAG, "Distributing tiles: "
+ + "[tilesPerPageCount=" + tilesPerPageCount + "]"
+ + "[totalTilesCount=" + totalTilesCount + "]"
+ );
+ }
+ for (int i = 0; i < totalTilesCount; i++) {
TileRecord tile = mTiles.get(i);
- if (mPages.get(index).mRecords.size() == tileCount) index++;
+ if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++;
if (DEBUG) {
Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
+ index);
@@ -577,8 +582,8 @@
});
setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
int dx = getWidth() * lastPageNumber;
- mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
- REVEAL_SCROLL_DURATION_MILLIS);
+ mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
+ REVEAL_SCROLL_DURATION_MILLIS);
postInvalidateOnAnimation();
}
@@ -738,6 +743,7 @@
public interface PageListener {
int INVALID_PAGE = -1;
+
void onPageChanged(boolean isFirst, int pageNumber);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index ef87fb4..dc9dcc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -29,6 +29,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.util.LargeScreenUtils;
import java.io.PrintWriter;
@@ -52,6 +53,7 @@
private boolean mQsDisabled;
private int mContentHorizontalPadding = -1;
private boolean mClippingEnabled;
+ private boolean mUseCombinedHeaders;
public QSContainerImpl(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -66,6 +68,10 @@
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
+ void setUseCombinedHeaders(boolean useCombinedHeaders) {
+ mUseCombinedHeaders = useCombinedHeaders;
+ }
+
@Override
public boolean hasOverlappingRendering() {
return false;
@@ -143,9 +149,15 @@
void updateResources(QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController) {
+ int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+ if (mUseCombinedHeaders
+ && !LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
+ topPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+ }
mQSPanelContainer.setPaddingRelative(
mQSPanelContainer.getPaddingStart(),
- QSUtils.getQsHeaderSystemIconsAreaHeight(mContext),
+ topPadding,
mQSPanelContainer.getPaddingEnd(),
mQSPanelContainer.getPaddingBottom());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index dea7bb5..28b4c822 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -22,6 +22,8 @@
import android.view.MotionEvent;
import android.view.View;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -37,7 +39,6 @@
private final ConfigurationController mConfigurationController;
private final FalsingManager mFalsingManager;
private final NonInterceptingScrollView mQSPanelContainer;
-
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
@@ -65,13 +66,15 @@
QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController,
ConfigurationController configurationController,
- FalsingManager falsingManager) {
+ FalsingManager falsingManager,
+ FeatureFlags featureFlags) {
super(view);
mQsPanelController = qsPanelController;
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
mConfigurationController = configurationController;
mFalsingManager = falsingManager;
mQSPanelContainer = mView.getQSPanelContainer();
+ view.setUseCombinedHeaders(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 920a108..d9be281 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -515,7 +515,13 @@
public void setExpanded(boolean expanded) {
if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
mQsExpanded = expanded;
- updateQsPanelControllerListening();
+ if (mInSplitShade && mQsExpanded) {
+ // in split shade QS is expanded immediately when shade expansion starts and then we
+ // also need to listen to changes - otherwise QS is updated only once its fully expanded
+ setListening(true);
+ } else {
+ updateQsPanelControllerListening();
+ }
updateQsState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index abc0ade..64962b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -237,7 +237,7 @@
* @return if bouncer is in transit
*/
public boolean isBouncerInTransit() {
- return mStatusBarKeyguardViewManager.isBouncerInTransit();
+ return mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2a80de0..dd88c83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.content.res.Configuration.Orientation;
import android.metrics.LogMaker;
+import android.util.Log;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +39,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileViewImpl;
import com.android.systemui.util.LargeScreenUtils;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -237,6 +239,16 @@
private void addTile(final QSTile tile, boolean collapsedView) {
final TileRecord r =
new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView));
+ // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
+ // b/250618218.
+ try {
+ QSTileViewImpl qsTileView = (QSTileViewImpl) (r.tileView);
+ if (qsTileView != null) {
+ qsTileView.setQsLogger(mQSLogger);
+ }
+ } catch (ClassCastException e) {
+ Log.e(TAG, "Failed to cast QSTileView to QSTileViewImpl", e);
+ }
mView.addTile(r);
mRecords.add(r);
mCachedSpecs = getTilesSpecs();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index f37d668..6240c10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -41,6 +41,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -53,7 +54,6 @@
import com.android.systemui.qs.nano.QsTileState;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 27d9da6..946fe54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -288,8 +288,15 @@
}
MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams();
- qqsLP.topMargin = largeScreenHeaderActive || !mUseCombinedQSHeader ? mContext.getResources()
- .getDimensionPixelSize(R.dimen.qqs_layout_margin_top) : qsOffsetHeight;
+ if (largeScreenHeaderActive) {
+ qqsLP.topMargin = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
+ } else if (!mUseCombinedQSHeader) {
+ qqsLP.topMargin = qsOffsetHeight;
+ } else {
+ qqsLP.topMargin = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
+ }
mHeaderQsPanel.setLayoutParams(qqsLP);
updateBatteryMode();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6b0abd4..7794fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -16,7 +16,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.database.ContentObserver;
import android.os.Handler;
@@ -47,10 +46,6 @@
this(settingsProxy, handler, settingName, userId, 0);
}
- public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
- this(settingsProxy, handler, settingName, ActivityManager.getCurrentUser());
- }
-
public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
int userId, int defaultValue) {
super(handler);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3d00dd4..7ee4047 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -123,7 +123,6 @@
public boolean updateResources() {
final Resources res = mContext.getResources();
mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
- updateColumns();
mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId);
mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
mSidePadding = useSidePadding() ? mCellMarginHorizontal / 2 : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index cf10c79..79fcc7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -82,12 +82,12 @@
DefaultItemAnimator animator = new DefaultItemAnimator();
animator.setMoveDuration(TileAdapter.MOVE_DURATION);
mRecyclerView.setItemAnimator(animator);
+
+ updateTransparentViewHeight();
}
void updateResources() {
- LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
- lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
- mTransparentView.setLayoutParams(lp);
+ updateTransparentViewHeight();
mRecyclerView.getAdapter().notifyItemChanged(0);
}
@@ -236,4 +236,10 @@
public boolean isOpening() {
return mOpening;
}
+
+ private void updateTransparentViewHeight() {
+ LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
+ lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+ mTransparentView.setLayoutParams(lp);
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 9ba3501..03bb7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -32,8 +32,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.FgsManagerController
@@ -42,10 +40,9 @@
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.domain.interactor.UserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -100,13 +97,12 @@
@Inject
constructor(
private val activityStarter: ActivityStarter,
- private val featureFlags: FeatureFlags,
private val metricsLogger: MetricsLogger,
private val uiEventLogger: UiEventLogger,
private val deviceProvisionedController: DeviceProvisionedController,
private val qsSecurityFooterUtils: QSSecurityFooterUtils,
private val fgsManagerController: FgsManagerController,
- private val userSwitchDialogController: UserSwitchDialogController,
+ private val userInteractor: UserInteractor,
securityRepository: SecurityRepository,
foregroundServicesRepository: ForegroundServicesRepository,
userSwitcherRepository: UserSwitcherRepository,
@@ -182,22 +178,6 @@
}
override fun showUserSwitcher(context: Context, expandable: Expandable) {
- if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- userSwitchDialogController.showDialog(context, expandable)
- return
- }
-
- val intent =
- Intent(context, UserSwitcherActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- expandable.activityLaunchController(),
- true /* showOverlockscreenwhenlocked */,
- UserHandle.SYSTEM,
- )
+ userInteractor.showUserSwitcher(context, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 931dc8d..9f6317f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -129,12 +129,36 @@
})
}
- fun logInternetTileUpdate(lastType: Int, callback: String) {
+ fun logInternetTileUpdate(tileSpec: String, lastType: Int, callback: String) {
log(VERBOSE, {
+ str1 = tileSpec
int1 = lastType
- str1 = callback
+ str2 = callback
}, {
- "mLastTileState=$int1, Callback=$str1."
+ "[$str1] mLastTileState=$int1, Callback=$str2."
+ })
+ }
+
+ // TODO(b/250618218): Remove this method once we know the root cause of b/250618218.
+ fun logTileBackgroundColorUpdateIfInternetTile(
+ tileSpec: String,
+ state: Int,
+ disabledByPolicy: Boolean,
+ color: Int
+ ) {
+ // This method is added to further debug b/250618218 which has only been observed from the
+ // InternetTile, so we are only logging the background color change for the InternetTile
+ // to avoid spamming the QSLogger.
+ if (tileSpec != "internet") {
+ return
+ }
+ log(VERBOSE, {
+ str1 = tileSpec
+ int1 = state
+ bool1 = disabledByPolicy
+ int2 = color
+ }, {
+ "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2."
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 1f92b12..cd69f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,16 +140,21 @@
iv.setTag(R.id.qs_icon_tag, icon);
iv.setTag(R.id.qs_slash_tag, state.slash);
iv.setPadding(0, padding, 0, padding);
- if (shouldAnimate && d instanceof Animatable2) {
+ if (d instanceof Animatable2) {
Animatable2 a = (Animatable2) d;
a.start();
- if (state.isTransient) {
- a.registerAnimationCallback(new AnimationCallback() {
- @Override
- public void onAnimationEnd(Drawable drawable) {
- a.start();
- }
- });
+ if (shouldAnimate) {
+ if (state.isTransient) {
+ a.registerAnimationCallback(new AnimationCallback() {
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ a.start();
+ }
+ });
+ }
+ } else {
+ // Sends animator to end of animation. Needs to be called after calling start.
+ a.stop();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 972b243..b355d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -50,6 +50,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.BooleanState
import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
import java.util.Objects
@@ -116,7 +117,7 @@
protected lateinit var sideView: ViewGroup
private lateinit var customDrawableView: ImageView
private lateinit var chevronView: ImageView
-
+ private var mQsLogger: QSLogger? = null
protected var showRippleEffect = true
private lateinit var ripple: RippleDrawable
@@ -188,6 +189,10 @@
updateHeight()
}
+ fun setQsLogger(qsLogger: QSLogger) {
+ mQsLogger = qsLogger
+ }
+
fun updateResources() {
FontSizeUtils.updateFontSize(label, R.dimen.qs_tile_text_size)
FontSizeUtils.updateFontSize(secondaryLabel, R.dimen.qs_tile_text_size)
@@ -493,6 +498,11 @@
// Colors
if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
singleAnimator.cancel()
+ mQsLogger?.logTileBackgroundColorUpdateIfInternetTile(
+ state.spec,
+ state.state,
+ state.disabledByPolicy,
+ getBackgroundColorForState(state.state, state.disabledByPolicy))
if (allowAnimations) {
singleAnimator.setValues(
colorValuesHolder(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 86d4fa3..033dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -48,6 +48,7 @@
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Inject;
@@ -74,14 +75,16 @@
QSLogger qsLogger,
BroadcastDispatcher broadcastDispatcher,
Lazy<ConnectivityManager> lazyConnectivityManager,
- GlobalSettings globalSettings
+ GlobalSettings globalSettings,
+ UserTracker userTracker
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBroadcastDispatcher = broadcastDispatcher;
mLazyConnectivityManager = lazyConnectivityManager;
- mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
+ mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
// mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index bebd580..5bc209a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -70,7 +70,7 @@
private final SettingObserver mDreamSettingObserver;
private final UserTracker mUserTracker;
private final boolean mDreamSupported;
- private final boolean mDreamOnlyEnabledForSystemUser;
+ private final boolean mDreamOnlyEnabledForDockUser;
private boolean mIsDocked = false;
@@ -100,22 +100,22 @@
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker,
@Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported,
- @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
- boolean dreamOnlyEnabledForSystemUser
+ @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+ boolean dreamOnlyEnabledForDockUser
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mDreamManager = dreamManager;
mBroadcastDispatcher = broadcastDispatcher;
mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
- Settings.Secure.SCREENSAVER_ENABLED) {
+ Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
refreshState();
}
};
mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
- Settings.Secure.SCREENSAVER_COMPONENTS) {
+ Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
refreshState();
@@ -123,7 +123,7 @@
};
mUserTracker = userTracker;
mDreamSupported = dreamSupported;
- mDreamOnlyEnabledForSystemUser = dreamOnlyEnabledForSystemUser;
+ mDreamOnlyEnabledForDockUser = dreamOnlyEnabledForDockUser;
}
@Override
@@ -203,7 +203,8 @@
// For now, restrict to debug users.
return Build.isDebuggable()
&& mDreamSupported
- && (!mDreamOnlyEnabledForSystemUser || mUserTracker.getUserHandle().isSystem());
+ // TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
+ && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserHandle().isSystem());
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index ae46477..350d8b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -387,7 +387,8 @@
@Override
protected void handleUpdateState(SignalState state, Object arg) {
- mQSLogger.logInternetTileUpdate(mLastTileState, arg == null ? "null" : arg.toString());
+ mQSLogger.logInternetTileUpdate(
+ getTileSpec(), mLastTileState, arg == null ? "null" : arg.toString());
if (arg instanceof CellularCallbackInfo) {
mLastTileState = LAST_STATE_CELLULAR;
handleUpdateCellularState(state, arg);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index b415022..376d3d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -115,7 +115,7 @@
state.label = mContext.getString(R.string.qr_code_scanner_title);
state.contentDescription = state.label;
state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
- state.state = mQRCodeScannerController.isEnabledForQuickSettings() ? Tile.STATE_ACTIVE
+ state.state = mQRCodeScannerController.isEnabledForQuickSettings() ? Tile.STATE_INACTIVE
: Tile.STATE_UNAVAILABLE;
}
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 f63f044..64a8a14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import android.app.Dialog;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
@@ -43,7 +44,6 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.screenrecord.RecordingController;
-import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -170,9 +170,9 @@
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
getHost().collapsePanels();
};
- ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
- mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
+ mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
ActivityStarter.OnDismissAction dismissAction = () -> {
if (shouldAnimateFromView) {
mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index a895d72..9743c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -249,15 +249,7 @@
mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
mInternetDialogTitle.setText(getDialogTitleText());
mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
-
- TypedArray typedArray = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.selectableItemBackground});
- try {
- mBackgroundOff = typedArray.getDrawable(0 /* index */);
- } finally {
- typedArray.recycle();
- }
-
+ mBackgroundOff = mContext.getDrawable(R.drawable.internet_dialog_selected_effect);
setOnClickListener();
mTurnWifiOnLayout.setBackground(null);
mAirplaneModeButton.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ba97297..547b496 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -78,8 +78,8 @@
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -90,12 +90,11 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -108,20 +107,19 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
import dagger.Lazy;
-
/**
* Class to send information from overview to launcher with a binder.
*/
@SysUISingleton
-public class OverviewProxyService extends CurrentUserTracker implements
- CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
- Dumpable {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
+ NavigationModeController.ModeChangedListener, Dumpable {
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
@@ -133,6 +131,7 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
+ private final Executor mMainExecutor;
private final ShellInterface mShellInterface;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private SysUiState mSysUiState;
@@ -145,6 +144,7 @@
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
private final CommandQueue mCommandQueue;
+ private final UserTracker mUserTracker;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
private final UiEventLogger mUiEventLogger;
@@ -417,7 +417,7 @@
return;
}
- mCurrentBoundedUserId = getCurrentUserId();
+ mCurrentBoundedUserId = mUserTracker.getUserId();
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
@@ -498,34 +498,44 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mConnectionBackoffAttempts = 0;
+ internalConnectToCurrentUser();
+ }
+ };
+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyService(Context context,
+ @Main Executor mainExecutor,
CommandQueue commandQueue,
ShellInterface shellInterface,
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
AssistUtils assistUtils,
DumpManager dumpManager) {
- super(broadcastDispatcher);
-
// b/241601880: This component shouldn't be running for a non-primary user
if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
}
mContext = context;
+ mMainExecutor = mainExecutor;
mShellInterface = shellInterface;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
mStatusBarWinController = statusBarWinController;
+ mUserTracker = userTracker;
mConnectionBackoffAttempts = 0;
mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
com.android.internal.R.string.config_recentsComponentName));
@@ -566,7 +576,7 @@
mCommandQueue = commandQueue;
// Listen for user setup
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
screenLifecycle.addObserver(mLifecycleObserver);
@@ -579,12 +589,6 @@
assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
}
- @Override
- public void onUserSwitched(int newUserId) {
- mConnectionBackoffAttempts = 0;
- internalConnectToCurrentUser();
- }
-
public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible)
.commitUpdate(mContext.getDisplayId());
@@ -712,7 +716,7 @@
mBound = mContext.bindServiceAsUser(launcherServiceIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- UserHandle.of(getCurrentUserId()));
+ UserHandle.of(mUserTracker.getUserId()));
} catch (SecurityException e) {
Log.e(TAG_OPS, "Unable to bind because of security error", e);
}
@@ -941,7 +945,7 @@
}
private void updateEnabledState() {
- final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
+ final int currentUser = mUserTracker.getUserId();
mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
MATCH_SYSTEM_ONLY, currentUser) != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
deleted file mode 100644
index 6de4648..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+++ /dev/null
@@ -1,53 +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.ripple
-
-/** A common utility functions that are used for computing [RippleShader]. */
-class RippleShaderUtilLibrary {
- //language=AGSL
- companion object {
- const val SHADER_LIB = """
- float triangleNoise(vec2 n) {
- n = fract(n * vec2(5.3987, 5.4421));
- n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
- float xy = n.x * n.y;
- return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
- }
- const float PI = 3.1415926535897932384626;
-
- float sparkles(vec2 uv, float t) {
- float n = triangleNoise(uv);
- float s = 0.0;
- for (float i = 0; i < 4; i += 1) {
- float l = i * 0.01;
- float h = l + 0.1;
- float o = smoothstep(n - l, h, n);
- o *= abs(sin(PI * o * (t + 0.55 * i)));
- s += o;
- }
- return s;
- }
-
- vec2 distort(vec2 p, float time, float distort_amount_radial,
- float distort_amount_xy) {
- float angle = atan(p.y, p.x);
- return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
- cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
- + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
- cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
- }"""
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
new file mode 100644
index 0000000..f4d59a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.screenrecord
+
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewStub
+import android.view.WindowManager
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Base permission dialog for screen share and recording */
+open class BaseScreenSharePermissionDialog(
+ context: Context?,
+ private val screenShareOptions: List<ScreenShareOption>,
+ private val appName: String?
+) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
+ private lateinit var dialogTitle: TextView
+ private lateinit var startButton: TextView
+ private lateinit var warning: TextView
+ private lateinit var screenShareModeSpinner: Spinner
+ var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window.apply {
+ addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ setGravity(Gravity.CENTER)
+ }
+ setContentView(R.layout.screen_share_dialog)
+ dialogTitle = findViewById(R.id.screen_share_dialog_title)
+ warning = findViewById(R.id.text_warning)
+ startButton = findViewById(R.id.button_start)
+ findViewById<TextView>(R.id.button_cancel).setOnClickListener { dismiss() }
+ initScreenShareOptions()
+ createOptionsView(getOptionsViewLayoutId())
+ }
+
+ protected fun initScreenShareOptions() {
+ selectedScreenShareOption = screenShareOptions.first()
+ warning.text = warningText
+ initScreenShareSpinner()
+ }
+
+ private val warningText: String
+ get() = context.getString(selectedScreenShareOption.warningText, appName)
+
+ private fun initScreenShareSpinner() {
+ val options = screenShareOptions.map { context.getString(it.spinnerText) }.toTypedArray()
+ val adapter =
+ ArrayAdapter(context.applicationContext, android.R.layout.simple_spinner_item, options)
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
+ screenShareModeSpinner.adapter = adapter
+ screenShareModeSpinner.onItemSelectedListener = this
+ }
+
+ override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
+ selectedScreenShareOption = screenShareOptions[pos]
+ warning.text = warningText
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+
+ /** Protected methods for the text updates & functionality */
+ protected fun setDialogTitle(@StringRes stringId: Int) {
+ val title = context.getString(stringId, appName)
+ dialogTitle.text = title
+ }
+
+ protected fun setStartButtonText(@StringRes stringId: Int) {
+ startButton.setText(stringId)
+ }
+
+ protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
+ startButton.setOnClickListener(listener)
+ }
+
+ // Create additional options that is shown under the share mode spinner
+ // Eg. the audio and tap toggles in SysUI Recorder
+ @LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null
+
+ private fun createOptionsView(@LayoutRes layoutId: Int?) {
+ if (layoutId == null) return
+ val stub = findViewById<View>(R.id.options_stub) as ViewStub
+ stub.layoutResource = layoutId
+ stub.inflate()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
new file mode 100644
index 0000000..15b0bc4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.screenrecord
+
+import android.content.Context
+import android.os.Bundle
+import com.android.systemui.R
+
+/** Dialog to select screen recording options */
+class MediaProjectionPermissionDialog(
+ context: Context?,
+ private val onStartRecordingClicked: Runnable,
+ appName: String?
+) : BaseScreenSharePermissionDialog(context, createOptionList(), appName) {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setDialogTitle(R.string.media_projection_permission_dialog_title)
+ setStartButtonText(R.string.media_projection_permission_dialog_continue)
+ setStartButtonOnClickListener {
+ // Note that it is important to run this callback before dismissing, so that the
+ // callback can disable the dialog exit animation if it wants to.
+ onStartRecordingClicked.run()
+ dismiss()
+ }
+ }
+
+ companion object {
+ private fun createOptionList(): List<ScreenShareOption> {
+ return listOf(
+ ScreenShareOption(
+ SINGLE_APP,
+ R.string.media_projection_permission_dialog_option_single_app,
+ R.string.media_projection_permission_dialog_warning_single_app
+ ),
+ ScreenShareOption(
+ ENTIRE_SCREEN,
+ R.string.media_projection_permission_dialog_option_entire_screen,
+ R.string.media_projection_permission_dialog_warning_entire_screen
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 1083f22..ce4e0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord;
+import android.app.Dialog;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -33,6 +34,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.policy.CallbackController;
@@ -97,11 +99,15 @@
}
/** Create a dialog to show screen recording options to the user. */
- public ScreenRecordDialog createScreenRecordDialog(Context context, FeatureFlags flags,
- DialogLaunchAnimator dialogLaunchAnimator, ActivityStarter activityStarter,
- @Nullable Runnable onStartRecordingClicked) {
- return new ScreenRecordDialog(context, this, activityStarter, mUserContextProvider,
- flags, dialogLaunchAnimator, onStartRecordingClicked);
+ public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
+ DialogLaunchAnimator dialogLaunchAnimator,
+ ActivityStarter activityStarter,
+ @Nullable Runnable onStartRecordingClicked) {
+ return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
+ ? new ScreenRecordPermissionDialog(context, this, activityStarter,
+ dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked)
+ : new ScreenRecordDialog(context, this, activityStarter,
+ mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
new file mode 100644
index 0000000..19bb15a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.screenrecord
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.ResultReceiver
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import android.widget.Switch
+import androidx.annotation.LayoutRes
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.media.MediaProjectionCaptureTarget
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserContextProvider
+
+/** Dialog to select screen recording options */
+class ScreenRecordPermissionDialog(
+ context: Context?,
+ private val controller: RecordingController,
+ private val activityStarter: ActivityStarter,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val userContextProvider: UserContextProvider,
+ private val onStartRecordingClicked: Runnable?
+) : BaseScreenSharePermissionDialog(context, createOptionList(), null) {
+ private lateinit var tapsSwitch: Switch
+ private lateinit var tapsView: View
+ private lateinit var audioSwitch: Switch
+ private lateinit var options: Spinner
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setDialogTitle(R.string.screenrecord_start_label)
+ setStartButtonText(R.string.screenrecord_start_recording)
+ setStartButtonOnClickListener { v: View? ->
+ onStartRecordingClicked?.run()
+ if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
+ requestScreenCapture(/* captureTarget= */ null)
+ }
+ if (selectedScreenShareOption.mode == SINGLE_APP) {
+ val intent = Intent(context, MediaProjectionAppSelectorActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ // We can't start activity for result here so we use result receiver to get
+ // the selected target to capture
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+ CaptureTargetResultReceiver()
+ )
+ val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
+ if (animationController == null) {
+ dismiss()
+ }
+ activityStarter.startActivity(intent, /* dismissShade= */ true, animationController)
+ }
+ dismiss()
+ }
+ initRecordOptionsView()
+ }
+
+ @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
+
+ private fun initRecordOptionsView() {
+ audioSwitch = findViewById(R.id.screenrecord_audio_switch)
+ tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
+ tapsView = findViewById(R.id.show_taps)
+ updateTapsViewVisibility()
+ options = findViewById(R.id.screen_recording_options)
+ val a: ArrayAdapter<*> =
+ ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
+ a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ options.adapter = a
+ options.setOnItemClickListenerInt { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
+ audioSwitch.isChecked = true
+ }
+ }
+
+ override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
+ super.onItemSelected(adapterView, view, pos, id)
+ updateTapsViewVisibility()
+ }
+
+ private fun updateTapsViewVisibility() {
+ tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE
+ }
+
+ /**
+ * Starts screen capture after some countdown
+ * @param captureTarget target to capture (could be e.g. a task) or null to record the whole
+ * screen
+ */
+ private fun requestScreenCapture(captureTarget: MediaProjectionCaptureTarget?) {
+ val userContext = userContextProvider.userContext
+ val showTaps = selectedScreenShareOption.mode != SINGLE_APP && tapsSwitch.isChecked
+ val audioMode =
+ if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource
+ else ScreenRecordingAudioSource.NONE
+ val startIntent =
+ PendingIntent.getForegroundService(
+ userContext,
+ RecordingService.REQUEST_CODE,
+ RecordingService.getStartIntent(
+ userContext,
+ Activity.RESULT_OK,
+ audioMode.ordinal,
+ showTaps,
+ captureTarget
+ ),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ val stopIntent =
+ PendingIntent.getService(
+ userContext,
+ RecordingService.REQUEST_CODE,
+ RecordingService.getStopIntent(userContext),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ controller.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent)
+ }
+
+ private inner class CaptureTargetResultReceiver() :
+ ResultReceiver(Handler(Looper.getMainLooper())) {
+ override fun onReceiveResult(resultCode: Int, resultData: Bundle) {
+ if (resultCode == Activity.RESULT_OK) {
+ val captureTarget =
+ resultData.getParcelable(
+ MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET,
+ MediaProjectionCaptureTarget::class.java
+ )
+
+ // Start recording of the selected target
+ requestScreenCapture(captureTarget)
+ }
+ }
+ }
+
+ companion object {
+ private val MODES =
+ listOf(
+ ScreenRecordingAudioSource.INTERNAL,
+ ScreenRecordingAudioSource.MIC,
+ ScreenRecordingAudioSource.MIC_AND_INTERNAL
+ )
+ private const val DELAY_MS: Long = 3000
+ private const val INTERVAL_MS: Long = 1000
+ private fun createOptionList(): List<ScreenShareOption> {
+ return listOf(
+ ScreenShareOption(
+ SINGLE_APP,
+ R.string.screenrecord_option_single_app,
+ R.string.screenrecord_warning_single_app
+ ),
+ ScreenShareOption(
+ ENTIRE_SCREEN,
+ R.string.screenrecord_option_entire_screen,
+ R.string.screenrecord_warning_entire_screen
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
new file mode 100644
index 0000000..914d29a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.screenrecord
+
+import androidx.annotation.IntDef
+import androidx.annotation.StringRes
+import kotlin.annotation.Retention
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(SINGLE_APP, ENTIRE_SCREEN)
+annotation class ScreenShareMode
+
+const val SINGLE_APP = 0
+const val ENTIRE_SCREEN = 1
+
+class ScreenShareOption(
+ @ScreenShareMode val mode: Int,
+ @StringRes val spinnerText: Int,
+ @StringRes val warningText: Int
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 5961635..01e32b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -32,7 +32,7 @@
import com.android.internal.infra.ServiceConnector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -45,7 +45,7 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val context: Context,
) {
/**
@@ -70,23 +70,21 @@
userId: Int,
overrideTransition: Boolean,
) {
- withContext(bgDispatcher) {
- dismissKeyguard()
+ dismissKeyguard()
- if (userId == UserHandle.myUserId()) {
- context.startActivity(intent, bundle)
- } else {
- launchCrossProfileIntent(userId, intent, bundle)
- }
+ if (userId == UserHandle.myUserId()) {
+ withContext(mainDispatcher) { context.startActivity(intent, bundle) }
+ } else {
+ launchCrossProfileIntent(userId, intent, bundle)
+ }
- if (overrideTransition) {
- val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
- } catch (e: Exception) {
- Log.e(TAG, "Error overriding screenshot app transition", e)
- }
+ if (overrideTransition) {
+ val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error overriding screenshot app transition", e)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 8bf956b..5450db9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -46,6 +46,8 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
import com.google.common.util.concurrent.ListenableFuture;
@@ -67,6 +69,7 @@
private static final String TAG = LogConfig.logTag(LongScreenshotActivity.class);
public static final String EXTRA_CAPTURE_RESPONSE = "capture-response";
+ public static final String EXTRA_SCREENSHOT_USER_HANDLE = "screenshot-userhandle";
private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path";
private final UiEventLogger mUiEventLogger;
@@ -74,6 +77,8 @@
private final Executor mBackgroundExecutor;
private final ImageExporter mImageExporter;
private final LongScreenshotData mLongScreenshotHolder;
+ private final ActionIntentExecutor mActionExecutor;
+ private final FeatureFlags mFeatureFlags;
private ImageView mPreview;
private ImageView mTransitionView;
@@ -85,6 +90,7 @@
private CropView mCropView;
private MagnifierView mMagnifierView;
private ScrollCaptureResponse mScrollCaptureResponse;
+ private UserHandle mScreenshotUserHandle;
private File mSavedImagePath;
private ListenableFuture<File> mCacheSaveFuture;
@@ -103,12 +109,15 @@
@Inject
public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
@Main Executor mainExecutor, @Background Executor bgExecutor,
- LongScreenshotData longScreenshotHolder) {
+ LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
+ FeatureFlags featureFlags) {
mUiEventLogger = uiEventLogger;
mUiExecutor = mainExecutor;
mBackgroundExecutor = bgExecutor;
mImageExporter = imageExporter;
mLongScreenshotHolder = longScreenshotHolder;
+ mActionExecutor = actionExecutor;
+ mFeatureFlags = featureFlags;
}
@@ -139,6 +148,11 @@
Intent intent = getIntent();
mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
+ mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE,
+ UserHandle.class);
+ if (mScreenshotUserHandle == null) {
+ mScreenshotUserHandle = Process.myUserHandle();
+ }
if (savedInstanceState != null) {
String savedImagePath = savedInstanceState.getString(KEY_SAVED_IMAGE_PATH);
@@ -318,36 +332,51 @@
}
private void doEdit(Uri uri) {
- String editorPackage = getString(R.string.config_screenshotEditor);
- Intent intent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- intent.setComponent(ComponentName.unflattenFromString(editorPackage));
- }
- intent.setDataAndType(uri, "image/png");
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) && mScreenshotUserHandle
+ != Process.myUserHandle()) {
+ // TODO: Fix transition for work profile. Omitting it in the meantime.
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createEditIntent(uri, this),
+ null,
+ mScreenshotUserHandle.getIdentifier(), false);
+ } else {
+ String editorPackage = getString(R.string.config_screenshotEditor);
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ intent.setDataAndType(uri, "image/png");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- mTransitionView.setImageBitmap(mOutputBitmap);
- mTransitionView.setVisibility(View.VISIBLE);
- mTransitionView.setTransitionName(
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
- // TODO: listen for transition completing instead of finishing onStop
- mTransitionStarted = true;
- startActivity(intent,
- ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
+ mTransitionView.setImageBitmap(mOutputBitmap);
+ mTransitionView.setVisibility(View.VISIBLE);
+ mTransitionView.setTransitionName(
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+ // TODO: listen for transition completing instead of finishing onStop
+ mTransitionStarted = true;
+ startActivity(intent,
+ ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
+ }
}
private void doShare(Uri uri) {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("image/png");
- intent.putExtra(Intent.EXTRA_STREAM, uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- Intent sharingChooserIntent = Intent.createChooser(intent, null)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null);
+ mActionExecutor.launchIntentAsync(shareIntent, null,
+ mScreenshotUserHandle.getIdentifier(), false);
+ } else {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("image/png");
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Intent sharingChooserIntent = Intent.createChooser(intent, null)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ }
}
private void onClicked(View v) {
@@ -389,8 +418,8 @@
mOutputBitmap = renderBitmap(drawable, bounds);
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(),
- // TODO: Owner must match the owner of the captured window.
- Process.myUserHandle());
+ mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+ ? mScreenshotUserHandle : Process.myUserHandle());
exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 7143ba2..b4934cf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -38,6 +38,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,7 +82,6 @@
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
private String mScreenshotId;
- private final boolean mSmartActionsEnabled;
private final Random mRandom = new Random();
private final Supplier<ActionTransition> mSharedElementTransition;
private final ImageExporter mImageExporter;
@@ -109,8 +109,6 @@
mParams = data;
// Initialize screenshot notification smart actions provider.
- mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
mSmartActionsProvider = screenshotNotificationSmartActionsProvider;
}
@@ -131,8 +129,16 @@
Bitmap image = mParams.image;
mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId);
+
+ boolean savingToOtherUser = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+ && (user != Process.myUserHandle());
+ // Smart actions don't yet work for cross-user saves.
+ boolean smartActionsEnabled = !savingToOtherUser
+ && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+ true);
try {
- if (mSmartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
+ if (smartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
// Since Quick Share target recommendation does not rely on image URL, it is
// queried and surfaced before image compress/export. Action intent would not be
// used, because it does not contain image URL.
@@ -150,10 +156,9 @@
CompletableFuture<List<Notification.Action>> smartActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS,
- mSmartActionsEnabled, user);
-
+ smartActionsEnabled, user);
List<Notification.Action> smartActions = new ArrayList<>();
- if (mSmartActionsEnabled) {
+ if (smartActionsEnabled) {
int timeoutMs = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
@@ -168,9 +173,12 @@
mImageData.uri = uri;
mImageData.owner = user;
mImageData.smartActions = smartActions;
- mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
- mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
- mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
+ mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
+ mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
+ mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
mImageData.quickShareAction = createQuickShareAction(mContext,
mQuickShareData.quickShareAction, uri);
mImageData.subject = getSubjectString();
@@ -228,7 +236,8 @@
* Assumes that the action intent is sent immediately after being supplied.
*/
@VisibleForTesting
- Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri) {
+ Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
return () -> {
ActionTransition transition = mSharedElementTransition.get();
@@ -274,7 +283,7 @@
.putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.setAction(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
@@ -290,7 +299,8 @@
}
@VisibleForTesting
- Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri) {
+ Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
return () -> {
ActionTransition transition = mSharedElementTransition.get();
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
@@ -323,7 +333,7 @@
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
@@ -339,7 +349,8 @@
}
@VisibleForTesting
- Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
+ Notification.Action createDeleteAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId();
@@ -350,7 +361,7 @@
.putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_ONE_SHOT
@@ -391,7 +402,7 @@
Intent intent = new Intent(context, SmartActionsReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
@@ -445,7 +456,9 @@
Intent intent = new Intent(context, SmartActionsReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ // We only query for quick share actions when smart actions are enabled, so we can assert
+ // that it's true here.
+ addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
@@ -464,7 +477,7 @@
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, null, image, mSmartActionsProvider,
QUICK_SHARE_ACTION,
- mSmartActionsEnabled, user);
+ true /* smartActionsEnabled */, user);
int timeoutMs = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 9b5295d..d94c827 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -63,6 +63,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -276,6 +277,7 @@
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
private final ActionIntentExecutor mActionExecutor;
+ private final UserManager mUserManager;
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
@@ -314,7 +316,8 @@
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
- ActionIntentExecutor actionExecutor
+ ActionIntentExecutor actionExecutor,
+ UserManager userManager
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
@@ -345,6 +348,7 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags;
mActionExecutor = actionExecutor;
+ mUserManager = userManager;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -587,7 +591,7 @@
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
withWindowAttached(() -> {
- requestScrollCapture();
+ requestScrollCapture(owner);
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
@@ -599,11 +603,11 @@
mScreenshotView.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
- mScreenshotHandler.postDelayed(
- ScreenshotController.this::requestScrollCapture, 150);
+ mScreenshotHandler.postDelayed(() -> {
+ requestScrollCapture(owner);
+ }, 150);
mScreenshotView.updateInsets(
- mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets());
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
if (mScreenshotAnimation != null
@@ -651,7 +655,7 @@
mScreenshotHandler.cancelTimeout(); // restarted after animation
}
- private void requestScrollCapture() {
+ private void requestScrollCapture(UserHandle owner) {
if (!allowLongScreenshots()) {
Log.d(TAG, "Long screenshots not supported on this device");
return;
@@ -664,10 +668,11 @@
mScrollCaptureClient.request(DEFAULT_DISPLAY);
mLastScrollCaptureRequest = future;
mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(future), mMainExecutor);
+ onScrollCaptureResponseReady(future, owner), mMainExecutor);
}
- private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {
+ private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
+ UserHandle owner) {
try {
if (mLastScrollCaptureResponse != null) {
mLastScrollCaptureResponse.close();
@@ -697,7 +702,7 @@
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
mScreenshotTakenInPortrait);
// delay starting scroll capture to make sure the scrim is up before the app moves
- mScreenshotView.post(() -> runBatchScrollCapture(response));
+ mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
@@ -706,7 +711,7 @@
ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
- private void runBatchScrollCapture(ScrollCaptureResponse response) {
+ private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
// Clear the reference to prevent close() in dismissScreenshot
mLastScrollCaptureResponse = null;
@@ -740,6 +745,8 @@
longScreenshot));
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+ intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
+ owner);
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -975,16 +982,25 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mScreenshotView.setChipIntents(imageData);
+ doPostAnimation(imageData);
}
});
} else {
- mScreenshotView.setChipIntents(imageData);
+ doPostAnimation(imageData);
}
});
}
}
+ private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
+ mScreenshotView.setChipIntents(imageData);
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+ // TODO: Read app from configuration
+ mScreenshotView.showWorkProfileMessage("Files");
+ }
+ }
+
/**
* Sets up the action shade and its entrance animation, once we get the Quick Share action data.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 8b5a24c..c891686 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -89,7 +89,9 @@
@UiEvent(doc = "User has saved a long screenshot to a file")
SCREENSHOT_LONG_SCREENSHOT_SAVED(910),
@UiEvent(doc = "User has discarded the result of a long screenshot")
- SCREENSHOT_LONG_SCREENSHOT_EXIT(911);
+ SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
+ @UiEvent(doc = "A screenshot has been taken and saved to work profile")
+ SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index c41e2bc..4cb91e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -15,12 +15,17 @@
*/
package com.android.systemui.screenshot
-import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.CentralSurfaces
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.util.Optional
import javax.inject.Inject
@@ -30,7 +35,8 @@
internal class ScreenshotProxyService @Inject constructor(
private val mExpansionMgr: ShadeExpansionStateManager,
private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
-) : Service() {
+ @Main private val mMainDispatcher: CoroutineDispatcher,
+) : LifecycleService() {
private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
/**
@@ -43,20 +49,28 @@
}
override fun dismissKeyguard(callback: IOnDoneCallback) {
- if (mCentralSurfacesOptional.isPresent) {
- mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard(
- Runnable {
- callback.onDone(true)
- }, null,
- true /* dismissShade */, true /* afterKeyguardGone */,
- true /* deferred */
- )
- } else {
- callback.onDone(false)
+ lifecycleScope.launch {
+ executeAfterDismissing(callback)
}
}
}
+ private suspend fun executeAfterDismissing(callback: IOnDoneCallback) =
+ withContext(mMainDispatcher) {
+ mCentralSurfacesOptional.ifPresentOrElse(
+ {
+ it.executeRunnableDismissingKeyguard(
+ Runnable {
+ callback.onDone(true)
+ }, null,
+ true /* dismissShade */, true /* afterKeyguardGone */,
+ true /* deferred */
+ )
+ },
+ { callback.onDone(false) }
+ )
+ }
+
override fun onBind(intent: Intent): IBinder? {
Log.d(TAG, "onBind: $intent")
return mBinder
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 27331ae..0a4b550 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -80,6 +80,7 @@
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -137,6 +138,8 @@
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
+ private ViewGroup mMessageContainer;
+ private TextView mMessageContent;
private ImageView mScreenshotPreview;
private ImageView mScreenshotBadge;
private View mScreenshotPreviewBorder;
@@ -340,10 +343,26 @@
}
}
+ /**
+ * Show a notification under the screenshot view indicating that a work profile screenshot has
+ * been taken and which app can be used to view it.
+ *
+ * @param appName The name of the app to use to view screenshots
+ */
+ void showWorkProfileMessage(String appName) {
+ mMessageContent.setText(
+ mContext.getString(R.string.screenshot_work_profile_notification, appName));
+ mMessageContainer.setVisibility(VISIBLE);
+ }
+
@Override // View
protected void onFinishInflate() {
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
+ mMessageContainer =
+ requireNonNull(mScreenshotStatic.findViewById(R.id.screenshot_message_container));
+ mMessageContent =
+ requireNonNull(mMessageContainer.findViewById(R.id.screenshot_message_content));
mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
mScreenshotPreviewBorder = requireNonNull(
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index 2d1d8b7..d33d113 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -29,6 +29,8 @@
import android.hardware.SensorPrivacyManager.Sources.DIALOG
import android.os.Bundle
import android.os.Handler
+import android.window.OnBackInvokedDispatcher
+import androidx.annotation.OpenForTesting
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
@@ -45,7 +47,8 @@
*
* <p>The dialog is started for the user the app is running for which might be a secondary users.
*/
-class SensorUseStartedActivity @Inject constructor(
+@OpenForTesting
+open class SensorUseStartedActivity @Inject constructor(
private val sensorPrivacyController: IndividualSensorPrivacyController,
private val keyguardStateController: KeyguardStateController,
private val keyguardDismissUtil: KeyguardDismissUtil,
@@ -67,9 +70,10 @@
private lateinit var sensorUsePackageName: String
private var unsuppressImmediately = false
- private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback
+ private var sensorPrivacyListener: IndividualSensorPrivacyController.Callback? = null
private var mDialog: AlertDialog? = null
+ private val mBackCallback = this::onBackInvoked
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -84,15 +88,14 @@
if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) {
sensor = ALL_SENSORS
- sensorPrivacyListener =
- IndividualSensorPrivacyController.Callback { _, _ ->
- if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
- !sensorPrivacyController.isSensorBlocked(CAMERA)) {
- finish()
- }
- }
-
- sensorPrivacyController.addCallback(sensorPrivacyListener)
+ val callback = IndividualSensorPrivacyController.Callback { _, _ ->
+ if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
+ !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+ finish()
+ }
+ }
+ sensorPrivacyListener = callback
+ sensorPrivacyController.addCallback(callback)
if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
!sensorPrivacyController.isSensorBlocked(CAMERA)) {
finish()
@@ -105,14 +108,14 @@
return
}
}
- sensorPrivacyListener =
- IndividualSensorPrivacyController.Callback { whichSensor: Int,
- isBlocked: Boolean ->
- if (whichSensor == sensor && !isBlocked) {
- finish()
- }
- }
- sensorPrivacyController.addCallback(sensorPrivacyListener)
+ val callback = IndividualSensorPrivacyController.Callback {
+ whichSensor: Int, isBlocked: Boolean ->
+ if (whichSensor == sensor && !isBlocked) {
+ finish()
+ }
+ }
+ sensorPrivacyListener = callback
+ sensorPrivacyController.addCallback(callback)
if (!sensorPrivacyController.isSensorBlocked(sensor)) {
finish()
@@ -122,6 +125,10 @@
mDialog = SensorUseDialog(this, sensor, this, this)
mDialog!!.show()
+
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mBackCallback)
}
override fun onStart() {
@@ -180,10 +187,15 @@
override fun onDestroy() {
super.onDestroy()
mDialog?.dismiss()
- sensorPrivacyController.removeCallback(sensorPrivacyListener)
+ sensorPrivacyListener?.also { sensorPrivacyController.removeCallback(it) }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback)
}
override fun onBackPressed() {
+ onBackInvoked()
+ }
+
+ fun onBackInvoked() {
// do not allow backing out
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
deleted file mode 100644
index dea8c32..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.settings;
-
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-/**
- * A class that has an observable for the current user.
- */
-public class CurrentUserObservable {
-
- private final CurrentUserTracker mTracker;
-
- private final MutableLiveData<Integer> mCurrentUser = new MutableLiveData<Integer>() {
- @Override
- protected void onActive() {
- super.onActive();
- mTracker.startTracking();
- }
-
- @Override
- protected void onInactive() {
- super.onInactive();
- mTracker.stopTracking();
- }
- };
-
- public CurrentUserObservable(BroadcastDispatcher broadcastDispatcher) {
- mTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser.setValue(newUserId);
- }
- };
- }
-
- /**
- * Returns the current user that can be observed.
- */
- public LiveData<Integer> getCurrentUser() {
- if (mCurrentUser.getValue() == null) {
- mCurrentUser.setValue(mTracker.getCurrentUserId());
- }
- return mCurrentUser;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
deleted file mode 100644
index 9599d77..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.settings;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public abstract class CurrentUserTracker {
- private final UserReceiver mUserReceiver;
-
- private Consumer<Integer> mCallback = this::onUserSwitched;
-
- public CurrentUserTracker(BroadcastDispatcher broadcastDispatcher) {
- this(UserReceiver.getInstance(broadcastDispatcher));
- }
-
- @VisibleForTesting
- CurrentUserTracker(UserReceiver receiver) {
- mUserReceiver = receiver;
- }
-
- public int getCurrentUserId() {
- return mUserReceiver.getCurrentUserId();
- }
-
- public void startTracking() {
- mUserReceiver.addTracker(mCallback);
- }
-
- public void stopTracking() {
- mUserReceiver.removeTracker(mCallback);
- }
-
- public abstract void onUserSwitched(int newUserId);
-
- @VisibleForTesting
- static class UserReceiver extends BroadcastReceiver {
- private static UserReceiver sInstance;
-
- private boolean mReceiverRegistered;
- private int mCurrentUserId;
- private final BroadcastDispatcher mBroadcastDispatcher;
-
- private List<Consumer<Integer>> mCallbacks = new ArrayList<>();
-
- @VisibleForTesting
- UserReceiver(BroadcastDispatcher broadcastDispatcher) {
- mBroadcastDispatcher = broadcastDispatcher;
- }
-
- static UserReceiver getInstance(BroadcastDispatcher broadcastDispatcher) {
- if (sInstance == null) {
- sInstance = new UserReceiver(broadcastDispatcher);
- }
- return sInstance;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- notifyUserSwitched(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- }
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- private void addTracker(Consumer<Integer> callback) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
- if (!mReceiverRegistered) {
- mCurrentUserId = ActivityManager.getCurrentUser();
- IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter, null,
- UserHandle.ALL);
- mReceiverRegistered = true;
- }
- }
-
- private void removeTracker(Consumer<Integer> callback) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- if (mCallbacks.size() == 0 && mReceiverRegistered) {
- mBroadcastDispatcher.unregisterReceiver(this);
- mReceiverRegistered = false;
- }
- }
- }
-
- private void notifyUserSwitched(int newUserId) {
- if (mCurrentUserId != newUserId) {
- mCurrentUserId = newUserId;
- List<Consumer<Integer>> callbacks = new ArrayList<>(mCallbacks);
- for (Consumer<Integer> consumer : callbacks) {
- // Accepting may modify this list
- if (mCallbacks.contains(consumer)) {
- consumer.accept(newUserId);
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 7801c68..5880003 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -21,6 +21,7 @@
import static com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -46,11 +47,13 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtilsInternal;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
@@ -74,9 +77,10 @@
private final Context mContext;
private final ToggleSlider mControl;
private final DisplayManager mDisplayManager;
- private final CurrentUserTracker mUserTracker;
+ private final UserTracker mUserTracker;
private final IVrManager mVrManager;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
@@ -169,7 +173,7 @@
}
mBrightnessObserver.startObserving();
- mUserTracker.startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
// Update the slider and mode before attaching the listener so we don't
// receive the onChanged notifications for the initial values.
@@ -197,7 +201,7 @@
}
mBrightnessObserver.stopObserving();
- mUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
}
@@ -275,22 +279,27 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mBackgroundHandler.post(mUpdateModeRunnable);
+ mBackgroundHandler.post(mUpdateSliderRunnable);
+ }
+ };
+
public BrightnessController(
Context context,
ToggleSlider control,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
mContext = context;
mControl = control;
mControl.setMax(GAMMA_SPACE_MAX);
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mBackgroundHandler.post(mUpdateModeRunnable);
- mBackgroundHandler.post(mUpdateSliderRunnable);
- }
- };
+ mUserTracker = userTracker;
mBrightnessObserver = new BrightnessObserver(mHandler);
mDisplayId = mContext.getDisplayId();
@@ -364,7 +373,7 @@
mControl.setEnforcedAdmin(
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
UserManager.DISALLOW_CONFIG_BRIGHTNESS,
- mUserTracker.getCurrentUserId()));
+ mUserTracker.getUserId()));
}
});
}
@@ -440,16 +449,19 @@
/** Factory for creating a {@link BrightnessController}. */
public static class Factory {
private final Context mContext;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
@Inject
public Factory(
Context context,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
mContext = context;
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
}
@@ -458,7 +470,8 @@
return new BrightnessController(
mContext,
toggleSlider,
- mBroadcastDispatcher,
+ mUserTracker,
+ mMainExecutor,
mBackgroundHandler);
}
}
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 d5a3954..e208be9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -34,10 +34,12 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -46,16 +48,19 @@
private BrightnessController mBrightnessController;
private final BrightnessSliderController.Factory mToggleSliderFactory;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
@Inject
public BrightnessDialog(
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
BrightnessSliderController.Factory factory,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mToggleSliderFactory = factory;
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
}
@@ -101,7 +106,7 @@
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
mBrightnessController = new BrightnessController(
- this, controller, mBroadcastDispatcher, mBackgroundHandler);
+ this, controller, mUserTracker, mMainExecutor, mBackgroundHandler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
new file mode 100644
index 0000000..fc61e90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
@@ -0,0 +1,73 @@
+/*
+ * 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.shade;
+
+import com.android.systemui.camera.CameraGestureHelper;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+
+import javax.inject.Inject;
+
+/** Handles launching camera from Shade. */
+@SysUISingleton
+public class CameraLauncher {
+ private final CameraGestureHelper mCameraGestureHelper;
+ private final KeyguardBypassController mKeyguardBypassController;
+
+ private boolean mLaunchingAffordance;
+
+ @Inject
+ public CameraLauncher(
+ CameraGestureHelper cameraGestureHelper,
+ KeyguardBypassController keyguardBypassController
+ ) {
+ mCameraGestureHelper = cameraGestureHelper;
+ mKeyguardBypassController = keyguardBypassController;
+ }
+
+ /** Launches the camera. */
+ public void launchCamera(int source, boolean isShadeFullyCollapsed) {
+ if (!isShadeFullyCollapsed) {
+ setLaunchingAffordance(true);
+ }
+
+ mCameraGestureHelper.launchCamera(source);
+ }
+
+ /**
+ * Set whether we are currently launching an affordance. This is currently only set when
+ * launched via a camera gesture.
+ */
+ public void setLaunchingAffordance(boolean launchingAffordance) {
+ mLaunchingAffordance = launchingAffordance;
+ mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+ }
+
+ /**
+ * Return true when a bottom affordance is launching an occluded activity with a splash screen.
+ */
+ public boolean isLaunchingAffordance() {
+ return mLaunchingAffordance;
+ }
+
+ /**
+ * Whether the camera application can be launched for the camera launch gesture.
+ */
+ public boolean canCameraGestureBeLaunched(int barState) {
+ return mCameraGestureHelper.canCameraGestureBeLaunched(barState);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 4063af3..5011227 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -51,6 +51,8 @@
connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+ constrainedWidth(R.id.date, true)
+ constrainedWidth(R.id.statusIcons, true)
}
)
}
@@ -92,7 +94,8 @@
centerEnd,
ConstraintSet.END
)
- constrainWidth(R.id.statusIcons, 0)
+ constrainedWidth(R.id.date, true)
+ constrainedWidth(R.id.statusIcons, true)
},
qsConstraintsChanges = {
setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
new file mode 100644
index 0000000..ae303eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -0,0 +1,142 @@
+/*
+ * 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.shade;
+
+import android.annotation.NonNull;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.keyguard.LockIconViewController;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Drawable for NotificationPanelViewController.
+ */
+public class DebugDrawable extends Drawable {
+
+ private final NotificationPanelViewController mNotificationPanelViewController;
+ private final NotificationPanelView mView;
+ private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+ private final LockIconViewController mLockIconViewController;
+ private final Set<Integer> mDebugTextUsedYPositions;
+ private final Paint mDebugPaint;
+
+ public DebugDrawable(
+ NotificationPanelViewController notificationPanelViewController,
+ NotificationPanelView notificationPanelView,
+ NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+ LockIconViewController lockIconViewController
+ ) {
+ mNotificationPanelViewController = notificationPanelViewController;
+ mView = notificationPanelView;
+ mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+ mLockIconViewController = lockIconViewController;
+ mDebugTextUsedYPositions = new HashSet<>();
+ mDebugPaint = new Paint();
+ }
+
+ @Override
+ public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
+ mDebugTextUsedYPositions.clear();
+
+ mDebugPaint.setColor(Color.RED);
+ mDebugPaint.setStrokeWidth(2);
+ mDebugPaint.setStyle(Paint.Style.STROKE);
+ mDebugPaint.setTextSize(24);
+ String headerDebugInfo = mNotificationPanelViewController.getHeaderDebugInfo();
+ if (headerDebugInfo != null) canvas.drawText(headerDebugInfo, 50, 100, mDebugPaint);
+
+ drawDebugInfo(canvas, mNotificationPanelViewController.getMaxPanelHeight(),
+ Color.RED, "getMaxPanelHeight()");
+ drawDebugInfo(canvas, (int) mNotificationPanelViewController.getExpandedHeight(),
+ Color.BLUE, "getExpandedHeight()");
+ drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+ Color.GREEN, "calculatePanelHeightQsExpanded()");
+ drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+ Color.YELLOW, "calculatePanelHeightShade()");
+ drawDebugInfo(canvas,
+ (int) mNotificationPanelViewController.calculateNotificationsTopPadding(),
+ Color.MAGENTA, "calculateNotificationsTopPadding()");
+ drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY,
+ Color.GRAY, "mClockPositionResult.clockY");
+ drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
+ "mLockIconViewController.getTop()");
+
+ if (mNotificationPanelViewController.getKeyguardShowing()) {
+ // Notifications have the space between those two lines.
+ drawDebugInfo(canvas,
+ mNotificationStackScrollLayoutController.getTop()
+ + (int) mNotificationPanelViewController
+ .getKeyguardNotificationTopPadding(),
+ Color.RED, "NSSL.getTop() + mKeyguardNotificationTopPadding");
+
+ drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom()
+ - (int) mNotificationPanelViewController
+ .getKeyguardNotificationBottomPadding(),
+ Color.RED, "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
+ }
+
+ mDebugPaint.setColor(Color.CYAN);
+ canvas.drawLine(0,
+ mNotificationPanelViewController.getClockPositionResult().stackScrollerPadding,
+ mView.getWidth(), mNotificationStackScrollLayoutController.getTopPadding(),
+ mDebugPaint);
+ }
+
+ private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
+ mDebugPaint.setColor(color);
+ canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
+ /* stopY= */ y, mDebugPaint);
+ canvas.drawText(label + " = " + y + "px", /* x= */ 0,
+ /* y= */ computeDebugYTextPosition(y), mDebugPaint);
+ }
+
+ private int computeDebugYTextPosition(int lineY) {
+ if (lineY - mDebugPaint.getTextSize() < 0) {
+ // Avoiding drawing out of bounds
+ lineY += mDebugPaint.getTextSize();
+ }
+ int textY = lineY;
+ while (mDebugTextUsedYPositions.contains(textY)) {
+ textY = (int) (textY + mDebugPaint.getTextSize());
+ }
+ mDebugTextUsedYPositions.add(textY);
+ return textY;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.UNKNOWN;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 6b540aa..31e4464 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -246,6 +246,8 @@
qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
if (header is MotionLayout) {
loadConstraints()
+ header.minHeight = resources
+ .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
lastInsets?.let { updateConstraintsForInsets(header, it) }
}
updateResources()
@@ -329,13 +331,8 @@
// Use resources.getXml instead of passing the resource id due to bug b/205018300
header.getConstraintSet(QQS_HEADER_CONSTRAINT)
.load(context, resources.getXml(R.xml.qqs_header))
- val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
- R.xml.qs_header_new
- } else {
- R.xml.qs_header
- }
header.getConstraintSet(QS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(qsConstraints))
+ .load(context, resources.getXml(R.xml.qs_header))
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
.load(context, resources.getXml(R.xml.large_screen_shade_header))
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 59b1ed7..7fbdeca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -56,15 +56,10 @@
import android.content.ContentResolver;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.ColorFilter;
import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.drawable.Drawable;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
@@ -123,17 +118,18 @@
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -181,6 +177,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -229,17 +226,15 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Provider;
@CentralSurfacesComponent.CentralSurfacesScope
-public final class NotificationPanelViewController {
+public final class NotificationPanelViewController implements Dumpable {
public static final String TAG = NotificationPanelView.class.getSimpleName();
public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
@@ -259,6 +254,8 @@
private static final int FLING_COLLAPSE = 1;
/** Fling until QS is completely hidden. */
private static final int FLING_HIDE = 2;
+ /** The delay to reset the hint text when the hint animation is finished running. */
+ private static final int HINT_RESET_DELAY_MS = 1200;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
- CollapsedStatusBarFragment.FADE_IN_DURATION
@@ -282,6 +279,11 @@
private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
private static final Rect EMPTY_RECT = new Rect();
+ /**
+ * Duration to use for the animator when the keyguard status view alignment changes, and a
+ * custom clock animation is in use.
+ */
+ private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
private final Resources mResources;
@@ -349,6 +351,7 @@
private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
private final FragmentListener mQsFragmentListener = new QsFragmentListener();
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+ private final NotificationGutsManager mGutsManager;
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
@@ -469,7 +472,6 @@
private boolean mCollapsedOnDown;
private boolean mClosingWithAlphaFadeOut;
private boolean mHeadsUpAnimatingAway;
- private boolean mLaunchingAffordance;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
@@ -580,7 +582,7 @@
/** Whether the current animator is resetting the pulse expansion after a drag down. */
private boolean mIsPulseExpansionResetAnimator;
- private final Rect mKeyguardStatusAreaClipBounds = new Rect();
+ private final Rect mLastQsClipBounds = new Rect();
private final Region mQsInterceptRegion = new Region();
/** Alpha of the views which only show on the keyguard but not in shade / shade locked. */
private float mKeyguardOnlyContentAlpha = 1.0f;
@@ -620,7 +622,6 @@
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final NPVCDownEventState.Buffer mLastDownEvents;
- private final CameraGestureHelper mCameraGestureHelper;
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private float mMinExpandHeight;
@@ -633,7 +634,6 @@
private float mLastGesturedOverExpansion = -1;
/** Whether the current animator is the spring back animation. */
private boolean mIsSpringBackAnimation;
- private boolean mInSplitShade;
private float mHintDistance;
private float mInitialOffsetOnTouch;
private boolean mCollapsedAndHeadsUpOnDown;
@@ -680,7 +680,7 @@
};
private final Runnable mMaybeHideExpandedRunnable = () -> {
if (getExpansionFraction() == 0.0f) {
- getView().post(mHideExpandedRunnable);
+ postToView(mHideExpandedRunnable);
}
};
@@ -710,6 +710,7 @@
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationGutsManager gutsManager,
NotificationsQSContainerController notificationsQSContainerController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@@ -748,9 +749,9 @@
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
- CameraGestureHelper cameraGestureHelper,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
- KeyguardBottomAreaInteractor keyguardBottomAreaInteractor) {
+ KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
+ DumpManager dumpManager) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
@@ -763,6 +764,7 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
+ mGutsManager = gutsManager;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -899,7 +901,8 @@
mView.setOnApplyWindowInsetsListener((v, insets) -> onApplyShadeWindowInsets(insets));
if (DEBUG_DRAWABLE) {
- mView.getOverlay().add(new DebugDrawable());
+ mView.getOverlay().add(new DebugDrawable(this, mView,
+ mNotificationStackScrollLayoutController, mLockIconViewController));
}
mKeyguardUnfoldTransition = unfoldComponent.map(
@@ -927,8 +930,8 @@
unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
- mCameraGestureHelper = cameraGestureHelper;
mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
+ dumpManager.registerDumpable(this);
}
private void unlockAnimationFinished() {
@@ -1068,7 +1071,6 @@
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
- mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1152,8 +1154,15 @@
mLargeScreenShadeHeaderHeight =
mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
- mQuickQsHeaderHeight = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
- SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
+ // TODO: When the flag is eventually removed, it means that we have a single view that is
+ // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually
+ // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class
+ // that controls the view as the offset needs to be the same regardless.
+ if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) {
+ mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
+ } else {
+ mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
+ }
int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
@@ -1308,7 +1317,11 @@
}
private void initBottomArea() {
- mKeyguardBottomArea.init(mKeyguardBottomAreaViewModel, mFalsingManager);
+ mKeyguardBottomArea.init(
+ mKeyguardBottomAreaViewModel,
+ mFalsingManager,
+ mLockIconViewController
+ );
}
@VisibleForTesting
@@ -1505,6 +1518,10 @@
updateClock();
}
+ public KeyguardClockPositionAlgorithm.Result getClockPositionResult() {
+ return mClockPositionResult;
+ }
+
@ClockSize
private int computeDesiredClockSize() {
if (mSplitShadeEnabled) {
@@ -1561,23 +1578,31 @@
// Find the clock, so we can exclude it from this transition.
FrameLayout clockContainerView =
mView.findViewById(R.id.lockscreen_clock_view_large);
- View clockView = clockContainerView.getChildAt(0);
- transition.excludeTarget(clockView, /* exclude= */ true);
+ // The clock container can sometimes be null. If it is, just fall back to the
+ // old animation rather than setting up the custom animations.
+ if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+ TransitionManager.beginDelayedTransition(
+ mNotificationContainerParent, transition);
+ } else {
+ View clockView = clockContainerView.getChildAt(0);
- TransitionSet set = new TransitionSet();
- set.addTransition(transition);
+ transition.excludeTarget(clockView, /* exclude= */ true);
- SplitShadeTransitionAdapter adapter =
- new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
- // Use linear here, so the actual clock can pick its own interpolator.
- adapter.setInterpolator(Interpolators.LINEAR);
- adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- adapter.addTarget(clockView);
- set.addTransition(adapter);
+ SplitShadeTransitionAdapter adapter =
+ new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
- TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ // Use linear here, so the actual clock can pick its own interpolator.
+ adapter.setInterpolator(Interpolators.LINEAR);
+ adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
+ adapter.addTarget(clockView);
+ set.addTransition(adapter);
+
+ TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ }
} else {
TransitionManager.beginDelayedTransition(
mNotificationContainerParent, transition);
@@ -1752,7 +1777,7 @@
}
public void resetViews(boolean animate) {
- mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
animateCloseQs(true /* animateAway */);
@@ -1828,6 +1853,10 @@
public void closeQs() {
cancelQsAnimation();
setQsExpansionHeight(mQsMinExpansionHeight);
+ // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
+ // middle of animation - we need to make sure that value is always false when shade if
+ // fully collapsed or expanded
+ setQsExpandImmediate(false);
}
@VisibleForTesting
@@ -1930,7 +1959,7 @@
// we want to perform an overshoot animation when flinging open
final boolean addOverscroll =
expand
- && !mInSplitShade // Split shade has its own overscroll logic
+ && !mSplitShadeEnabled // Split shade has its own overscroll logic
&& mStatusBarStateController.getState() != KEYGUARD
&& mOverExpansion == 0.0f
&& vel >= 0;
@@ -2719,8 +2748,10 @@
* as well based on the bounds of the shade and QS state.
*/
private void setQSClippingBounds() {
- final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
- final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
+ float qsExpansionFraction = computeQsExpansionFraction();
+ final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+ final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
+ checkCorrectScrimVisibility(qsExpansionFraction);
int top = calculateTopQsClippingBound(qsPanelBottomY);
int bottom = calculateBottomQsClippingBound(top);
@@ -2731,6 +2762,19 @@
applyQSClippingBounds(left, top, right, bottom, qsVisible);
}
+ private void checkCorrectScrimVisibility(float expansionFraction) {
+ // issues with scrims visible on keyguard occur only in split shade
+ if (mSplitShadeEnabled) {
+ boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1;
+ // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
+ // on QS expansion
+ if (expansionFraction == 1 && keyguardViewsVisible) {
+ Log.wtf(TAG,
+ "Incorrect state, scrim is visible at the same time when clock is visible");
+ }
+ }
+ }
+
private int calculateTopQsClippingBound(int qsPanelBottomY) {
int top;
if (mSplitShadeEnabled) {
@@ -2771,7 +2815,7 @@
return top + mNotificationStackScrollLayoutController.getHeight()
+ mSplitShadeNotificationsScrimMarginBottom;
} else {
- return getView().getBottom();
+ return mView.getBottom();
}
}
@@ -2786,7 +2830,7 @@
private int calculateRightQsClippingBound() {
if (mIsFullWidth) {
- return getView().getRight() + mDisplayRightInset;
+ return mView.getRight() + mDisplayRightInset;
} else {
return mNotificationStackScrollLayoutController.getRight();
}
@@ -2801,7 +2845,7 @@
*/
private void applyQSClippingBounds(int left, int top, int right, int bottom,
boolean qsVisible) {
- if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) {
+ if (!mAnimateNextNotificationBounds || mLastQsClipBounds.isEmpty()) {
if (mQsClippingAnimation != null) {
// update the end position of the animator
mQsClippingAnimationEndBounds.set(left, top, right, bottom);
@@ -2810,10 +2854,10 @@
}
} else {
mQsClippingAnimationEndBounds.set(left, top, right, bottom);
- final int startLeft = mKeyguardStatusAreaClipBounds.left;
- final int startTop = mKeyguardStatusAreaClipBounds.top;
- final int startRight = mKeyguardStatusAreaClipBounds.right;
- final int startBottom = mKeyguardStatusAreaClipBounds.bottom;
+ final int startLeft = mLastQsClipBounds.left;
+ final int startTop = mLastQsClipBounds.top;
+ final int startRight = mLastQsClipBounds.right;
+ final int startBottom = mLastQsClipBounds.bottom;
if (mQsClippingAnimation != null) {
mQsClippingAnimation.cancel();
}
@@ -2850,12 +2894,10 @@
private void applyQSClippingImmediately(int left, int top, int right, int bottom,
boolean qsVisible) {
- // Fancy clipping for quick settings
int radius = mScrimCornerRadius;
boolean clipStatusView = false;
+ mLastQsClipBounds.set(left, top, right, bottom);
if (mIsFullWidth) {
- // The padding on this area is large enough that we can use a cheaper clipping strategy
- mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
clipStatusView = qsVisible;
float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
@@ -2890,8 +2932,8 @@
radius,
qsVisible && !mSplitShadeEnabled);
}
- mKeyguardStatusViewController.setClipBounds(
- clipStatusView ? mKeyguardStatusAreaClipBounds : null);
+ // The padding on this area is large enough that we can use a cheaper clipping strategy
+ mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
if (!qsVisible && mSplitShadeEnabled) {
// On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
// be visible, otherwise you can see the bounds once swiping up to see bouncer
@@ -2969,7 +3011,7 @@
}
}
- private float calculateNotificationsTopPadding() {
+ float calculateNotificationsTopPadding() {
if (mSplitShadeEnabled) {
return mKeyguardShowing ? getKeyguardNotificationStaticPadding() : 0;
}
@@ -3003,6 +3045,18 @@
}
}
+ public boolean getKeyguardShowing() {
+ return mKeyguardShowing;
+ }
+
+ public float getKeyguardNotificationTopPadding() {
+ return mKeyguardNotificationTopPadding;
+ }
+
+ public float getKeyguardNotificationBottomPadding() {
+ return mKeyguardNotificationBottomPadding;
+ }
+
/** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
private int getKeyguardNotificationStaticPadding() {
if (!mKeyguardShowing) {
@@ -3272,7 +3326,6 @@
return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
}
- @VisibleForTesting
int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
@@ -3370,11 +3423,7 @@
boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
if (mPanelExpanded != isExpanded) {
mPanelExpanded = isExpanded;
-
- mHeadsUpManager.setIsPanelExpanded(isExpanded);
- mStatusBarTouchableRegionManager.setPanelExpanded(isExpanded);
- mCentralSurfaces.setPanelExpanded(isExpanded);
-
+ mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
if (!isExpanded && mQs != null && mQs.isCustomizing()) {
mQs.closeCustomizer();
}
@@ -3398,7 +3447,7 @@
}
}
- private int calculatePanelHeightQsExpanded() {
+ int calculatePanelHeightQsExpanded() {
float
notificationHeight =
mNotificationStackScrollLayoutController.getHeight()
@@ -3465,13 +3514,17 @@
}
private float getHeaderTranslation() {
+ if (mSplitShadeEnabled) {
+ // in split shade QS don't translate, just (un)squish and overshoot
+ return 0;
+ }
if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
return -mQs.getQsMinExpansionHeight();
}
float appearAmount = mNotificationStackScrollLayoutController
.calculateAppearFraction(mExpandedHeight);
float startHeight = -mQsExpansionHeight;
- if (!mSplitShadeEnabled && mBarState == StatusBarState.SHADE) {
+ if (mBarState == StatusBarState.SHADE) {
// Small parallax as we pull down and clip QS
startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT;
}
@@ -3673,7 +3726,6 @@
private void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
mTracking = false;
- mCentralSurfaces.onTrackingStopped(expand);
updatePanelExpansionAndVisibility();
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -3716,14 +3768,16 @@
@VisibleForTesting
void onUnlockHintFinished() {
- mCentralSurfaces.onHintFinished();
+ // Delay the reset a bit so the user can read the text.
+ mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
mScrimController.setExpansionAffectsAlpha(true);
mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
}
@VisibleForTesting
void onUnlockHintStarted() {
- mCentralSurfaces.onUnlockHintStarted();
+ mFalsingCollector.onUnlockHintStarted();
+ mKeyguardIndicationController.showActionToUnlock();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
}
@@ -3928,6 +3982,10 @@
}
}
+ public int getBarState() {
+ return mBarState;
+ }
+
private boolean isOnKeyguard() {
return mBarState == KEYGUARD;
}
@@ -3973,35 +4031,6 @@
&& mBarState == StatusBarState.SHADE;
}
- /** Launches the camera. */
- public void launchCamera(int source) {
- if (!isFullyCollapsed()) {
- setLaunchingAffordance(true);
- }
-
- mCameraGestureHelper.launchCamera(source);
- }
-
- public void onAffordanceLaunchEnded() {
- setLaunchingAffordance(false);
- }
-
- /** Set whether we are currently launching an affordance (i.e. camera gesture). */
- private void setLaunchingAffordance(boolean launchingAffordance) {
- mLaunchingAffordance = launchingAffordance;
- mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
- }
-
- /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */
- public boolean isLaunchingAffordanceWithPreview() {
- return mLaunchingAffordance;
- }
-
- /** Whether the camera application can be launched by the camera launch gesture. */
- public boolean canCameraGestureBeLaunched() {
- return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
- }
-
public boolean hideStatusBarIconsWhenExpanded() {
if (mIsLaunchAnimationRunning) {
return mHideIconsDuringLaunchAnimation;
@@ -4267,29 +4296,184 @@
mBlockingExpansionForCurrentTouch = mTracking;
}
+ @Override
public void dump(PrintWriter pw, String[] args) {
- pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
- + " tracking=%s timeAnim=%s%s "
- + "touchDisabled=%s" + "]",
- this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
- mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
- ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
- mTouchDisabled ? "T" : "f"));
+ pw.println(TAG + ":");
IndentingPrintWriter ipw = asIndenting(pw);
ipw.increaseIndent();
+
+ ipw.print("mDownTime="); ipw.println(mDownTime);
+ ipw.print("mTouchSlopExceededBeforeDown="); ipw.println(mTouchSlopExceededBeforeDown);
+ ipw.print("mIsLaunchAnimationRunning="); ipw.println(mIsLaunchAnimationRunning);
+ ipw.print("mOverExpansion="); ipw.println(mOverExpansion);
+ ipw.print("mExpandedHeight="); ipw.println(mExpandedHeight);
+ ipw.print("mTracking="); ipw.println(mTracking);
+ ipw.print("mHintAnimationRunning="); ipw.println(mHintAnimationRunning);
+ ipw.print("mExpanding="); ipw.println(mExpanding);
+ ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
+ ipw.print("mKeyguardNotificationBottomPadding=");
+ ipw.println(mKeyguardNotificationBottomPadding);
+ ipw.print("mKeyguardNotificationTopPadding="); ipw.println(mKeyguardNotificationTopPadding);
+ ipw.print("mMaxAllowedKeyguardNotifications=");
+ ipw.println(mMaxAllowedKeyguardNotifications);
+ ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate);
+ ipw.print("mQuickQsHeaderHeight="); ipw.println(mQuickQsHeaderHeight);
+ ipw.print("mQsTrackingPointer="); ipw.println(mQsTrackingPointer);
+ ipw.print("mQsTracking="); ipw.println(mQsTracking);
+ ipw.print("mConflictingQsExpansionGesture="); ipw.println(mConflictingQsExpansionGesture);
+ ipw.print("mPanelExpanded="); ipw.println(mPanelExpanded);
+ ipw.print("mQsExpanded="); ipw.println(mQsExpanded);
+ ipw.print("mQsExpandedWhenExpandingStarted="); ipw.println(mQsExpandedWhenExpandingStarted);
+ ipw.print("mQsFullyExpanded="); ipw.println(mQsFullyExpanded);
+ ipw.print("mKeyguardShowing="); ipw.println(mKeyguardShowing);
+ ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled);
+ ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled);
+ ipw.print("mDozing="); ipw.println(mDozing);
+ ipw.print("mDozingOnDown="); ipw.println(mDozingOnDown);
+ ipw.print("mBouncerShowing="); ipw.println(mBouncerShowing);
+ ipw.print("mBarState="); ipw.println(mBarState);
+ ipw.print("mInitialHeightOnTouch="); ipw.println(mInitialHeightOnTouch);
+ ipw.print("mInitialTouchX="); ipw.println(mInitialTouchX);
+ ipw.print("mInitialTouchY="); ipw.println(mInitialTouchY);
+ ipw.print("mQsExpansionHeight="); ipw.println(mQsExpansionHeight);
+ ipw.print("mQsMinExpansionHeight="); ipw.println(mQsMinExpansionHeight);
+ ipw.print("mQsMaxExpansionHeight="); ipw.println(mQsMaxExpansionHeight);
+ ipw.print("mQsPeekHeight="); ipw.println(mQsPeekHeight);
+ ipw.print("mStackScrollerOverscrolling="); ipw.println(mStackScrollerOverscrolling);
+ ipw.print("mQsExpansionFromOverscroll="); ipw.println(mQsExpansionFromOverscroll);
+ ipw.print("mLastOverscroll="); ipw.println(mLastOverscroll);
+ ipw.print("mQsExpansionEnabledPolicy="); ipw.println(mQsExpansionEnabledPolicy);
+ ipw.print("mQsExpansionEnabledAmbient="); ipw.println(mQsExpansionEnabledAmbient);
+ ipw.print("mStatusBarMinHeight="); ipw.println(mStatusBarMinHeight);
+ ipw.print("mStatusBarHeaderHeightKeyguard="); ipw.println(mStatusBarHeaderHeightKeyguard);
+ ipw.print("mOverStretchAmount="); ipw.println(mOverStretchAmount);
+ ipw.print("mDownX="); ipw.println(mDownX);
+ ipw.print("mDownY="); ipw.println(mDownY);
+ ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
+ ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
+ ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight);
+ ipw.print("mSplitShadeNotificationsScrimMarginBottom=");
+ ipw.println(mSplitShadeNotificationsScrimMarginBottom);
+ ipw.print("mIsExpanding="); ipw.println(mIsExpanding);
+ ipw.print("mQsExpandImmediate="); ipw.println(mQsExpandImmediate);
+ ipw.print("mTwoFingerQsExpandPossible="); ipw.println(mTwoFingerQsExpandPossible);
+ ipw.print("mHeaderDebugInfo="); ipw.println(mHeaderDebugInfo);
+ ipw.print("mQsAnimatorExpand="); ipw.println(mQsAnimatorExpand);
+ ipw.print("mQsScrimEnabled="); ipw.println(mQsScrimEnabled);
+ ipw.print("mQsTouchAboveFalsingThreshold="); ipw.println(mQsTouchAboveFalsingThreshold);
+ ipw.print("mQsFalsingThreshold="); ipw.println(mQsFalsingThreshold);
+ ipw.print("mHeadsUpStartHeight="); ipw.println(mHeadsUpStartHeight);
+ ipw.print("mListenForHeadsUp="); ipw.println(mListenForHeadsUp);
+ ipw.print("mNavigationBarBottomHeight="); ipw.println(mNavigationBarBottomHeight);
+ ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
+ ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
+ ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
+ ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
+ ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
+ ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
+ ipw.print("mAmbientIndicationBottomPadding="); ipw.println(mAmbientIndicationBottomPadding);
+ ipw.print("mIsFullWidth="); ipw.println(mIsFullWidth);
+ ipw.print("mBlockingExpansionForCurrentTouch=");
+ ipw.println(mBlockingExpansionForCurrentTouch);
+ ipw.print("mExpectingSynthesizedDown="); ipw.println(mExpectingSynthesizedDown);
+ ipw.print("mLastEventSynthesizedDown="); ipw.println(mLastEventSynthesizedDown);
+ ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount);
+ ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount);
+ ipw.print("mPulsing="); ipw.println(mPulsing);
+ ipw.print("mHideIconsDuringLaunchAnimation="); ipw.println(mHideIconsDuringLaunchAnimation);
+ ipw.print("mStackScrollerMeasuringPass="); ipw.println(mStackScrollerMeasuringPass);
+ ipw.print("mPanelAlpha="); ipw.println(mPanelAlpha);
+ ipw.print("mBottomAreaShadeAlpha="); ipw.println(mBottomAreaShadeAlpha);
+ ipw.print("mHeadsUpInset="); ipw.println(mHeadsUpInset);
+ ipw.print("mHeadsUpPinnedMode="); ipw.println(mHeadsUpPinnedMode);
+ ipw.print("mAllowExpandForSmallExpansion="); ipw.println(mAllowExpandForSmallExpansion);
+ ipw.print("mLockscreenNotificationQSPadding=");
+ ipw.println(mLockscreenNotificationQSPadding);
+ ipw.print("mTransitioningToFullShadeProgress=");
+ ipw.println(mTransitioningToFullShadeProgress);
+ ipw.print("mTransitionToFullShadeQSPosition=");
+ ipw.println(mTransitionToFullShadeQSPosition);
+ ipw.print("mDistanceForQSFullShadeTransition=");
+ ipw.println(mDistanceForQSFullShadeTransition);
+ ipw.print("mQsTranslationForFullShadeTransition=");
+ ipw.println(mQsTranslationForFullShadeTransition);
+ ipw.print("mMaxOverscrollAmountForPulse="); ipw.println(mMaxOverscrollAmountForPulse);
+ ipw.print("mAnimateNextNotificationBounds="); ipw.println(mAnimateNextNotificationBounds);
+ ipw.print("mNotificationBoundsAnimationDelay=");
+ ipw.println(mNotificationBoundsAnimationDelay);
+ ipw.print("mNotificationBoundsAnimationDuration=");
+ ipw.println(mNotificationBoundsAnimationDuration);
+ ipw.print("mIsPanelCollapseOnQQS="); ipw.println(mIsPanelCollapseOnQQS);
+ ipw.print("mAnimatingQS="); ipw.println(mAnimatingQS);
+ ipw.print("mIsQsTranslationResetAnimator="); ipw.println(mIsQsTranslationResetAnimator);
+ ipw.print("mIsPulseExpansionResetAnimator="); ipw.println(mIsPulseExpansionResetAnimator);
+ ipw.print("mKeyguardOnlyContentAlpha="); ipw.println(mKeyguardOnlyContentAlpha);
+ ipw.print("mKeyguardOnlyTransitionTranslationY=");
+ ipw.println(mKeyguardOnlyTransitionTranslationY);
+ ipw.print("mUdfpsMaxYBurnInOffset="); ipw.println(mUdfpsMaxYBurnInOffset);
+ ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation);
+ ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection);
+ ipw.print("mScrimCornerRadius="); ipw.println(mScrimCornerRadius);
+ ipw.print("mScreenCornerRadius="); ipw.println(mScreenCornerRadius);
+ ipw.print("mQSAnimatingHiddenFromCollapsed="); ipw.println(mQSAnimatingHiddenFromCollapsed);
+ ipw.print("mUseLargeScreenShadeHeader="); ipw.println(mUseLargeScreenShadeHeader);
+ ipw.print("mEnableQsClipping="); ipw.println(mEnableQsClipping);
+ ipw.print("mQsClipTop="); ipw.println(mQsClipTop);
+ ipw.print("mQsClipBottom="); ipw.println(mQsClipBottom);
+ ipw.print("mQsVisible="); ipw.println(mQsVisible);
+ ipw.print("mMinFraction="); ipw.println(mMinFraction);
+ ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered);
+ ipw.print("mSplitShadeFullTransitionDistance=");
+ ipw.println(mSplitShadeFullTransitionDistance);
+ ipw.print("mSplitShadeScrimTransitionDistance=");
+ ipw.println(mSplitShadeScrimTransitionDistance);
+ ipw.print("mMinExpandHeight="); ipw.println(mMinExpandHeight);
+ ipw.print("mPanelUpdateWhenAnimatorEnds="); ipw.println(mPanelUpdateWhenAnimatorEnds);
+ ipw.print("mHasVibratedOnOpen="); ipw.println(mHasVibratedOnOpen);
+ ipw.print("mFixedDuration="); ipw.println(mFixedDuration);
+ ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
+ ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
+ ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
+ ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
+ ipw.print("mHintDistance="); ipw.println(mHintDistance);
+ ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
+ ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
+ ipw.print("mExpandedFraction="); ipw.println(mExpandedFraction);
+ ipw.print("mExpansionDragDownAmountPx="); ipw.println(mExpansionDragDownAmountPx);
+ ipw.print("mPanelClosedOnDown="); ipw.println(mPanelClosedOnDown);
+ ipw.print("mHasLayoutedSinceDown="); ipw.println(mHasLayoutedSinceDown);
+ ipw.print("mUpdateFlingVelocity="); ipw.println(mUpdateFlingVelocity);
+ ipw.print("mUpdateFlingOnLayout="); ipw.println(mUpdateFlingOnLayout);
+ ipw.print("mClosing="); ipw.println(mClosing);
+ ipw.print("mTouchSlopExceeded="); ipw.println(mTouchSlopExceeded);
+ ipw.print("mTrackingPointer="); ipw.println(mTrackingPointer);
+ ipw.print("mTouchSlop="); ipw.println(mTouchSlop);
+ ipw.print("mSlopMultiplier="); ipw.println(mSlopMultiplier);
+ ipw.print("mTouchAboveFalsingThreshold="); ipw.println(mTouchAboveFalsingThreshold);
+ ipw.print("mTouchStartedInEmptyArea="); ipw.println(mTouchStartedInEmptyArea);
+ ipw.print("mMotionAborted="); ipw.println(mMotionAborted);
+ ipw.print("mUpwardsWhenThresholdReached="); ipw.println(mUpwardsWhenThresholdReached);
+ ipw.print("mAnimatingOnDown="); ipw.println(mAnimatingOnDown);
+ ipw.print("mHandlingPointerUp="); ipw.println(mHandlingPointerUp);
+ ipw.print("mInstantExpanding="); ipw.println(mInstantExpanding);
+ ipw.print("mAnimateAfterExpanding="); ipw.println(mAnimateAfterExpanding);
+ ipw.print("mIsFlinging="); ipw.println(mIsFlinging);
+ ipw.print("mViewName="); ipw.println(mViewName);
+ ipw.print("mInitialExpandY="); ipw.println(mInitialExpandY);
+ ipw.print("mInitialExpandX="); ipw.println(mInitialExpandX);
+ ipw.print("mTouchDisabled="); ipw.println(mTouchDisabled);
+ ipw.print("mInitialTouchFromKeyguard="); ipw.println(mInitialTouchFromKeyguard);
+ ipw.print("mNextCollapseSpeedUpFactor="); ipw.println(mNextCollapseSpeedUpFactor);
+ ipw.print("mGestureWaitForTouchSlop="); ipw.println(mGestureWaitForTouchSlop);
+ ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
+ ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
+ ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
- ipw.println("applyQSClippingImmediately: top(" + mQsClipTop + ") bottom(" + mQsClipBottom
- + ")");
- ipw.println("qsVisible:" + mQsVisible);
new DumpsysTableLogger(
TAG,
NPVCDownEventState.TABLE_HEADERS,
mLastDownEvents.toList()
).printTableData(ipw);
- ipw.decreaseIndent();
- if (mKeyguardStatusBarViewController != null) {
- mKeyguardStatusBarViewController.dump(pw, args);
- }
}
@@ -4362,6 +4546,10 @@
if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
}
+ public String getHeaderDebugInfo() {
+ return mHeaderDebugInfo;
+ }
+
public void onThemeChanged() {
mConfigurationListener.onThemeChanged();
}
@@ -4615,7 +4803,6 @@
}
mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isFalsingThresholdNeeded(),
mCentralSurfaces.isWakeUpComingFromTouch());
// Log collapse gesture if on lock screen.
if (!expand && onKeyguard) {
@@ -4637,7 +4824,7 @@
mUpdateFlingVelocity = vel;
}
} else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateBouncer()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
onEmptySpaceClick();
onTrackingStopped(true);
@@ -4664,9 +4851,6 @@
*/
private boolean isFalseTouch(float x, float y,
@Classifier.InteractionType int interactionType) {
- if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
- return false;
- }
if (mFalsingManager.isClassifierEnabled()) {
return mFalsingManager.isFalseTouch(interactionType);
}
@@ -4764,7 +4948,7 @@
float maxPanelHeight = getMaxPanelTransitionDistance();
if (mHeightAnimator == null) {
// Split shade has its own overscroll logic
- if (mTracking && !mInSplitShade) {
+ if (mTracking && !mSplitShadeEnabled) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
}
@@ -4811,7 +4995,6 @@
setExpandedHeight(getMaxPanelTransitionDistance() * frac);
}
- @VisibleForTesting
float getExpandedHeight() {
return mExpandedHeight;
}
@@ -5016,6 +5199,26 @@
return mView;
}
+ /** */
+ public boolean postToView(Runnable action) {
+ return mView.post(action);
+ }
+
+ /** */
+ public boolean sendInterceptTouchEventToView(MotionEvent event) {
+ return mView.onInterceptTouchEvent(event);
+ }
+
+ /** */
+ public void requestLayoutOnView() {
+ mView.requestLayout();
+ }
+
+ /** */
+ public void resetViewAlphas() {
+ ViewGroupFadeHelper.reset(mView);
+ }
+
private void beginJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
@@ -5313,14 +5516,23 @@
// - from SHADE to KEYGUARD
// - from SHADE_LOCKED to SHADE
// - getting notified again about the current SHADE or KEYGUARD state
+ if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
+ // user can go to keyguard from different shade states and closing animation
+ // may not fully run - we always want to make sure we close QS when that happens
+ // as we never need QS open in fresh keyguard state
+ closeQs();
+ }
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
if (!animatingUnlockedShadeToKeyguard) {
// Only make the status bar visible if we're not animating the screen off, since
// we only want to be showing the clock/notifications during the animation.
- mShadeLog.v("Updating keyguard status bar state to "
- + (keyguardShowing ? "visible" : "invisible"));
+ if (keyguardShowing) {
+ mShadeLog.v("Updating keyguard status bar state to visible");
+ } else {
+ mShadeLog.v("Updating keyguard status bar state to invisible");
+ }
mKeyguardStatusBarViewController.updateViewState(
/* alpha= */ 1f,
keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -5513,89 +5725,6 @@
}
}
- private final class DebugDrawable extends Drawable {
- private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>();
- private final Paint mDebugPaint = new Paint();
-
- @Override
- public void draw(@NonNull Canvas canvas) {
- mDebugTextUsedYPositions.clear();
-
- mDebugPaint.setColor(Color.RED);
- mDebugPaint.setStrokeWidth(2);
- mDebugPaint.setStyle(Paint.Style.STROKE);
- mDebugPaint.setTextSize(24);
- if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, mDebugPaint);
-
- drawDebugInfo(canvas, getMaxPanelHeight(), Color.RED, "getMaxPanelHeight()");
- drawDebugInfo(canvas, (int) getExpandedHeight(), Color.BLUE, "getExpandedHeight()");
- drawDebugInfo(canvas, calculatePanelHeightQsExpanded(), Color.GREEN,
- "calculatePanelHeightQsExpanded()");
- drawDebugInfo(canvas, calculatePanelHeightShade(), Color.YELLOW,
- "calculatePanelHeightShade()");
- drawDebugInfo(canvas, (int) calculateNotificationsTopPadding(), Color.MAGENTA,
- "calculateNotificationsTopPadding()");
- drawDebugInfo(canvas, mClockPositionResult.clockY, Color.GRAY,
- "mClockPositionResult.clockY");
- drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
- "mLockIconViewController.getTop()");
-
- if (mKeyguardShowing) {
- // Notifications have the space between those two lines.
- drawDebugInfo(canvas,
- mNotificationStackScrollLayoutController.getTop() +
- (int) mKeyguardNotificationTopPadding,
- Color.RED,
- "NSSL.getTop() + mKeyguardNotificationTopPadding");
-
- drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom() -
- (int) mKeyguardNotificationBottomPadding,
- Color.RED,
- "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
- }
-
- mDebugPaint.setColor(Color.CYAN);
- canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
- mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint);
- }
-
- private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
- mDebugPaint.setColor(color);
- canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
- /* stopY= */ y, mDebugPaint);
- canvas.drawText(label + " = " + y + "px", /* x= */ 0,
- /* y= */ computeDebugYTextPosition(y), mDebugPaint);
- }
-
- private int computeDebugYTextPosition(int lineY) {
- if (lineY - mDebugPaint.getTextSize() < 0) {
- // Avoiding drawing out of bounds
- lineY += mDebugPaint.getTextSize();
- }
- int textY = lineY;
- while (mDebugTextUsedYPositions.contains(textY)) {
- textY = (int) (textY + mDebugPaint.getTextSize());
- }
- mDebugTextUsedYPositions.add(textY);
- return textY;
- }
-
- @Override
- public void setAlpha(int alpha) {
-
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
-
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.UNKNOWN;
- }
- }
-
@NonNull
private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) {
// the same types of insets that are handled in NotificationShadeWindowView
@@ -6105,7 +6234,7 @@
== AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
|| action
== AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
- mStatusBarKeyguardViewManager.showBouncer(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
return true;
}
return super.performAccessibilityAction(host, action, args);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 66a22f4..8698c04 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -44,6 +44,7 @@
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -158,6 +159,7 @@
SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
configurationController.addCallback(this);
shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
+ shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
float desiredPreferredRefreshRate = context.getResources()
.getInteger(R.integer.config_keyguardRefreshRate);
@@ -204,6 +206,14 @@
}
}
+ @VisibleForTesting
+ void onShadeExpansionFullyChanged(Boolean isExpanded) {
+ if (mCurrentState.mPanelExpanded != isExpanded) {
+ mCurrentState.mPanelExpanded = isExpanded;
+ apply(mCurrentState);
+ }
+ }
+
/**
* Register a listener to monitor scrims visibility
* @param listener A listener to monitor scrims visibility
@@ -236,7 +246,6 @@
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
mLp.setFitInsetsTypes(0 /* types */);
- mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("NotificationShade");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -374,8 +383,6 @@
mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
-
- mLpChanged.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
private void applyForceShowNavigationFlag(State state) {
@@ -699,15 +706,6 @@
}
@Override
- public void setPanelExpanded(boolean isExpanded) {
- if (mCurrentState.mPanelExpanded == isExpanded) {
- return;
- }
- mCurrentState.mPanelExpanded = isExpanded;
- apply(mCurrentState);
- }
-
- @Override
public void onRemoteInputActive(boolean remoteInputActive) {
mCurrentState.mRemoteInputActive = remoteInputActive;
apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index e52170e..6acf417 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -16,6 +16,7 @@
package com.android.systemui.shade;
+import static android.os.Trace.TRACE_TAG_ALWAYS;
import static android.view.WindowInsets.Type.systemBars;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
@@ -23,6 +24,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -33,7 +35,9 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Trace;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.ActionMode;
import android.view.DisplayCutout;
import android.view.InputQueue;
@@ -72,6 +76,7 @@
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
private InteractionEventHandler mInteractionEventHandler;
+ private LayoutInsetsController mLayoutInsetProvider;
public NotificationShadeWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -106,12 +111,10 @@
mLeftInset = 0;
mRightInset = 0;
DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
- if (displayCutout != null) {
- mLeftInset = displayCutout.getSafeInsetLeft();
- mRightInset = displayCutout.getSafeInsetRight();
- }
- mLeftInset = Math.max(insets.left, mLeftInset);
- mRightInset = Math.max(insets.right, mRightInset);
+ Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
+ .getinsets(windowInsets, displayCutout);
+ mLeftInset = pairInsets.first;
+ mRightInset = pairInsets.second;
applyMargins();
return windowInsets;
}
@@ -170,6 +173,10 @@
mInteractionEventHandler = listener;
}
+ protected void setLayoutInsetsController(LayoutInsetsController provider) {
+ mLayoutInsetProvider = provider;
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
@@ -299,6 +306,19 @@
return mode;
}
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationShadeWindowView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
+
+ @Override
+ public void requestLayout() {
+ Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout");
+ super.requestLayout();
+ }
+
private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
private final ActionMode.Callback mWrapped;
@@ -338,6 +358,18 @@
}
}
+ /**
+ * Controller responsible for calculating insets for the shade window.
+ */
+ public interface LayoutInsetsController {
+
+ /**
+ * Update the insets and calculate them accordingly.
+ */
+ Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout);
+ }
+
interface InteractionEventHandler {
/**
* Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 1e63b2d..d773c01 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -76,6 +77,7 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
+ private final NotificationInsetsController mNotificationInsetsController;
private GestureDetector mPulsingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -111,6 +113,7 @@
CentralSurfaces centralSurfaces,
NotificationShadeWindowController controller,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener,
FeatureFlags featureFlags,
@@ -134,6 +137,7 @@
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
+ mNotificationInsetsController = notificationInsetsController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -165,6 +169,7 @@
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
+ mView.setLayoutInsetsController(mNotificationInsetsController);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
@@ -284,7 +289,7 @@
return true;
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
// capture all touches if the alt auth bouncer is showing
return true;
}
@@ -311,7 +316,7 @@
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation);
+ mNotificationPanelViewController.sendInterceptTouchEventToView(cancellation);
cancellation.recycle();
}
@@ -322,7 +327,7 @@
handled = !mService.isPulsing();
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
// eat the touch
handled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index eaf7fae..d783293 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -160,7 +160,7 @@
if (getCentralSurfaces().getNotificationShadeWindowView()
.isVisibleToUser()) {
getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
- getNotificationPanelViewController().getView().post(executable);
+ getNotificationPanelViewController().postToView(executable);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 667392c..a1767cc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -34,6 +34,7 @@
class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
+ private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>()
private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@@ -62,6 +63,15 @@
expansionListeners.remove(listener)
}
+ fun addFullExpansionListener(listener: ShadeFullExpansionListener) {
+ fullExpansionListeners.add(listener)
+ listener.onShadeExpansionFullyChanged(qsExpanded)
+ }
+
+ fun removeFullExpansionListener(listener: ShadeFullExpansionListener) {
+ fullExpansionListeners.remove(listener)
+ }
+
fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
qsExpansionListeners.add(listener)
listener.onQsExpansionChanged(qsExpanded)
@@ -156,6 +166,13 @@
qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
}
+ fun onShadeExpansionFullyChanged(isExpanded: Boolean) {
+ this.expanded = isExpanded
+
+ debugLog("expanded=$isExpanded")
+ fullExpansionListeners.forEach { it.onShadeExpansionFullyChanged(isExpanded) }
+ }
+
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
new file mode 100644
index 0000000..6d13e19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.shade
+
+/** A listener interface to be notified of expansion events for the notification shade. */
+fun interface ShadeFullExpansionListener {
+ /** Invoked whenever the shade expansion changes, when it is fully collapsed or expanded */
+ fun onShadeExpansionFullyChanged(isExpanded: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f2b8603..0f27420 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -36,7 +36,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
@@ -55,7 +55,6 @@
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -64,11 +63,11 @@
import android.os.UserManager;
import android.text.TextUtils;
import android.text.format.Formatter;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -76,6 +75,8 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
@@ -123,7 +124,6 @@
private static final String TAG = "KeyguardIndication";
private static final boolean DEBUG_CHARGING_SPEED = false;
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final int MSG_HIDE_TRANSIENT = 1;
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
@@ -139,6 +139,7 @@
protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AuthController mAuthController;
+ private final KeyguardLogger mKeyguardLogger;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -155,11 +156,12 @@
private final AccessibilityManager mAccessibilityManager;
private final Handler mHandler;
- protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
+ @VisibleForTesting
+ public KeyguardIndicationRotateTextViewController mRotateTextViewController;
private BroadcastReceiver mBroadcastReceiver;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private String mRestingIndication;
+ private String mPersistentUnlockMessage;
private String mAlignmentIndication;
private CharSequence mTrustGrantedIndication;
private CharSequence mTransientIndication;
@@ -196,7 +198,9 @@
public void onScreenTurnedOn() {
mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
if (mBiometricErrorMessageToShowOnScreenOn != null) {
- showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
+ String followUpMessage = mFaceLockedOutThisAuthSession
+ ? faceLockedOutFollowupMessage() : null;
+ showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
// We want to keep this message around in case the screen was off
hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
mBiometricErrorMessageToShowOnScreenOn = null;
@@ -229,7 +233,8 @@
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
AccessibilityManager accessibilityManager,
- FaceHelpMessageDeferral faceHelpMessageDeferral) {
+ FaceHelpMessageDeferral faceHelpMessageDeferral,
+ KeyguardLogger keyguardLogger) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
@@ -249,6 +254,7 @@
mKeyguardBypassController = keyguardBypassController;
mAccessibilityManager = accessibilityManager;
mScreenLifecycle = screenLifecycle;
+ mKeyguardLogger = keyguardLogger;
mScreenLifecycle.addObserver(mScreenObserver);
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
@@ -373,7 +379,7 @@
updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
updateLockScreenAlignmentMsg();
updateLockScreenLogoutView();
- updateLockScreenRestingMsg();
+ updateLockScreenPersistentUnlockMsg();
}
private void updateOrganizedOwnedDevice() {
@@ -479,7 +485,8 @@
}
private void updateLockScreenUserLockedMsg(int userId) {
- if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
+ if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)
+ || mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
new KeyguardIndication.Builder()
@@ -584,18 +591,17 @@
}
}
- private void updateLockScreenRestingMsg() {
- if (!TextUtils.isEmpty(mRestingIndication)
- && !mRotateTextViewController.hasIndications()) {
+ private void updateLockScreenPersistentUnlockMsg() {
+ if (!TextUtils.isEmpty(mPersistentUnlockMessage)) {
mRotateTextViewController.updateIndication(
- INDICATION_TYPE_RESTING,
+ INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
new KeyguardIndication.Builder()
- .setMessage(mRestingIndication)
+ .setMessage(mPersistentUnlockMessage)
.setTextColor(mInitialTextColorState)
.build(),
- false);
+ true);
} else {
- mRotateTextViewController.hideIndication(INDICATION_TYPE_RESTING);
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
}
}
@@ -678,11 +684,8 @@
}
}
- /**
- * Sets the indication that is shown if nothing else is showing.
- */
- public void setRestingIndication(String restingIndication) {
- mRestingIndication = restingIndication;
+ private void setPersistentUnlockMessage(String persistentUnlockMessage) {
+ mPersistentUnlockMessage = persistentUnlockMessage;
updateDeviceEntryIndication(false);
}
@@ -925,7 +928,7 @@
}
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
return; // udfps affordance is highlighted, no need to show action to unlock
} else if (mKeyguardUpdateMonitor.isFaceEnrolled()) {
String message = mContext.getString(R.string.keyguard_retry);
@@ -1024,7 +1027,7 @@
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
} catch (RemoteException e) {
- Log.e(TAG, "Error calling IBatteryStats: ", e);
+ mKeyguardLogger.logException(e, "Error calling IBatteryStats");
mChargingTimeRemaining = -1;
}
updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
@@ -1072,8 +1075,10 @@
final boolean isCoExFaceAcquisitionMessage =
faceAuthSoftError && isUnlockWithFingerprintPossible;
if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
- debugLog("skip showing msgId=" + msgId + " helpString=" + helpString
- + ", due to co-ex logic");
+ mKeyguardLogger.logBiometricMessage(
+ "skipped showing help message due to co-ex logic",
+ msgId,
+ helpString);
} else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
mInitialTextColorState);
@@ -1114,6 +1119,9 @@
public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
mFaceLockedOutThisAuthSession = false;
+ } else if (biometricSourceType == FINGERPRINT) {
+ setPersistentUnlockMessage(mKeyguardUpdateMonitor.isFingerprintLockedOut()
+ ? mContext.getString(R.string.keyguard_unlock) : "");
}
}
@@ -1131,7 +1139,7 @@
CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
- debugLog("suppressingFaceError msgId=" + msgId + " errString= " + errString);
+ mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -1145,8 +1153,9 @@
private void onFingerprintAuthError(int msgId, String errString) {
if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
- debugLog("suppressingFingerprintError msgId=" + msgId
- + " errString= " + errString);
+ mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
+ msgId,
+ errString);
} else {
showErrorMessageNowOrLater(errString, null);
}
@@ -1179,16 +1188,14 @@
@Override
public void onTrustChanged(int userId) {
- if (getCurrentUser() != userId) {
- return;
- }
+ if (!isCurrentUser(userId)) return;
updateDeviceEntryIndication(false);
}
@Override
- public void showTrustGrantedMessage(CharSequence message) {
- mTrustGrantedIndication = message;
- updateDeviceEntryIndication(false);
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ @NonNull TrustGrantFlags flags, @Nullable String message) {
+ showTrustGrantedMessage(dismissKeyguard, message);
}
@Override
@@ -1248,10 +1255,17 @@
}
}
+ private boolean isCurrentUser(int userId) {
+ return getCurrentUser() == userId;
+ }
+
+ protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) {
+ mTrustGrantedIndication = message;
+ updateDeviceEntryIndication(false);
+ }
+
private void handleFaceLockoutError(String errString) {
- int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
- : R.string.keyguard_unlock;
- String followupMessage = mContext.getString(followupMsgId);
+ String followupMessage = faceLockedOutFollowupMessage();
// Lockout error can happen multiple times in a session because we trigger face auth
// even when it is locked out so that the user is aware that face unlock would have
// triggered but didn't because it is locked out.
@@ -1263,18 +1277,26 @@
showErrorMessageNowOrLater(errString, followupMessage);
} else if (!mAuthController.isUdfpsFingerDown()) {
// On subsequent lockouts, we show a more generic locked out message.
- showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable),
+ showErrorMessageNowOrLater(
+ mContext.getString(R.string.keyguard_face_unlock_unavailable),
followupMessage);
}
}
+ private String faceLockedOutFollowupMessage() {
+ int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+ : R.string.keyguard_unlock;
+ return mContext.getString(followupMsgId);
+ }
+
private static boolean isLockoutError(int msgId) {
return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
|| msgId == FaceManager.FACE_ERROR_LOCKOUT;
}
private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
- debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
+ mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
+ null, String.valueOf(deferredFaceMessage));
if (canUnlockWithFingerprint()) {
// Co-ex: show deferred message OR nothing
// if we're on the lock screen (bouncer isn't showing), show the deferred msg
@@ -1286,7 +1308,8 @@
);
} else {
// otherwise, don't show any message
- debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
+ mKeyguardLogger.logBiometricMessage(
+ "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
}
} else if (deferredFaceMessage != null) {
// Face-only: The face timeout message is not very actionable, let's ask the
@@ -1307,12 +1330,6 @@
KeyguardUpdateMonitor.getCurrentUser());
}
- private void debugLog(String logMsg) {
- if (DEBUG) {
- Log.d(TAG, logMsg);
- }
- }
-
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9d2750f..bc456d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -18,6 +18,8 @@
import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
+import com.android.systemui.util.leak.RotationUtils
+import com.android.systemui.util.leak.RotationUtils.Rotation
import java.util.function.Consumer
/**
@@ -67,22 +69,19 @@
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
val ovalWidthIncreaseAmount =
- getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
+ getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
val initialWidthMultiplier = (1f - OVAL_INITIAL_WIDTH_PERCENT) / 2f
with(scrim) {
- revealGradientEndColorAlpha = 1f - getPercentPastThreshold(
- amount, FADE_END_COLOR_OUT_THRESHOLD)
+ revealGradientEndColorAlpha =
+ 1f - getPercentPastThreshold(amount, FADE_END_COLOR_OUT_THRESHOLD)
setRevealGradientBounds(
- scrim.width * initialWidthMultiplier +
- -scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_TOP_PERCENT -
- scrim.height * interpolatedAmount,
- scrim.width * (1f - initialWidthMultiplier) +
- scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_BOTTOM_PERCENT +
- scrim.height * interpolatedAmount)
+ scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
+ scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
+ scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
+ scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+ )
}
}
}
@@ -97,12 +96,17 @@
scrim.interpolatedRevealAmount = interpolatedAmount
scrim.startColorAlpha =
- getPercentPastThreshold(1 - interpolatedAmount,
- threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+ getPercentPastThreshold(
+ 1 - interpolatedAmount,
+ threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+ )
scrim.revealGradientEndColorAlpha =
- 1f - getPercentPastThreshold(interpolatedAmount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE)
+ 1f -
+ getPercentPastThreshold(
+ interpolatedAmount,
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ )
// Start changing gradient bounds later to avoid harsh gradient in the beginning
val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1.0f, interpolatedAmount)
@@ -179,7 +183,7 @@
*/
private val OFF_SCREEN_START_AMOUNT = 0.05f
- private val WIDTH_INCREASE_MULTIPLIER = 1.25f
+ private val INCREASE_MULTIPLIER = 1.25f
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN_REVERSE.getInterpolation(amount)
@@ -188,15 +192,36 @@
with(scrim) {
revealGradientEndColorAlpha = 1f - fadeAmount
interpolatedRevealAmount = interpolatedAmount
- setRevealGradientBounds(
+ @Rotation val rotation = RotationUtils.getRotation(scrim.getContext())
+ if (rotation == RotationUtils.ROTATION_NONE) {
+ setRevealGradientBounds(
width * (1f + OFF_SCREEN_START_AMOUNT) -
- width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY -
- height * interpolatedAmount,
+ width * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY - height * interpolatedAmount,
width * (1f + OFF_SCREEN_START_AMOUNT) +
- width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY +
- height * interpolatedAmount)
+ width * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY + height * interpolatedAmount
+ )
+ } else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
+ setRevealGradientBounds(
+ powerButtonY - width * interpolatedAmount,
+ (-height * OFF_SCREEN_START_AMOUNT) -
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY + width * interpolatedAmount,
+ (-height * OFF_SCREEN_START_AMOUNT) +
+ height * INCREASE_MULTIPLIER * interpolatedAmount
+ )
+ } else {
+ // RotationUtils.ROTATION_SEASCAPE
+ setRevealGradientBounds(
+ (width - powerButtonY) - width * interpolatedAmount,
+ height * (1f + OFF_SCREEN_START_AMOUNT) -
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
+ (width - powerButtonY) + width * interpolatedAmount,
+ height * (1f + OFF_SCREEN_START_AMOUNT) +
+ height * INCREASE_MULTIPLIER * interpolatedAmount
+ )
+ }
}
}
}
@@ -208,9 +233,7 @@
*/
class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- /**
- * Listener that is called if the scrim's opaqueness changes
- */
+ /** Listener that is called if the scrim's opaqueness changes */
lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
@@ -224,8 +247,11 @@
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
- Trace.traceCounter(Trace.TRACE_TAG_APP, "light_reveal_amount",
- (field * 100).toInt())
+ Trace.traceCounter(
+ Trace.TRACE_TAG_APP,
+ "light_reveal_amount",
+ (field * 100).toInt()
+ )
invalidate()
}
}
@@ -250,10 +276,10 @@
/**
* Alpha of the fill that can be used in the beginning of the animation to hide the content.
- * Normally the gradient bounds are animated from small size so the content is not visible,
- * but if the start gradient bounds allow to see some content this could be used to make the
- * reveal smoother. It can help to add fade in effect in the beginning of the animation.
- * The color of the fill is determined by [revealGradientEndColor].
+ * Normally the gradient bounds are animated from small size so the content is not visible, but
+ * if the start gradient bounds allow to see some content this could be used to make the reveal
+ * smoother. It can help to add fade in effect in the beginning of the animation. The color of
+ * the fill is determined by [revealGradientEndColor].
*
* 0 - no fill and content is visible, 1 - the content is covered with the start color
*/
@@ -281,9 +307,7 @@
}
}
- /**
- * Is the scrim currently fully opaque
- */
+ /** Is the scrim currently fully opaque */
var isScrimOpaque = false
private set(value) {
if (field != value) {
@@ -318,16 +342,22 @@
* Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
* via local matrix in [onDraw] so we never need to construct a new shader.
*/
- private val gradientPaint = Paint().apply {
- shader = RadialGradient(
- 0f, 0f, 1f,
- intArrayOf(Color.TRANSPARENT, Color.WHITE), floatArrayOf(0f, 1f),
- Shader.TileMode.CLAMP)
+ private val gradientPaint =
+ Paint().apply {
+ shader =
+ RadialGradient(
+ 0f,
+ 0f,
+ 1f,
+ intArrayOf(Color.TRANSPARENT, Color.WHITE),
+ floatArrayOf(0f, 1f),
+ Shader.TileMode.CLAMP
+ )
- // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
- // window, rather than outright replacing them.
- xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
- }
+ // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
+ // window, rather than outright replacing them.
+ xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
+ }
/**
* Matrix applied to [gradientPaint]'s RadialGradient shader to move the gradient to
@@ -347,8 +377,8 @@
* simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
* [revealGradientHeight] for you.
*
- * This method does not call [invalidate] - you should do so once you're done changing
- * properties.
+ * This method does not call [invalidate]
+ * - you should do so once you're done changing properties.
*/
fun setRevealGradientBounds(left: Float, top: Float, right: Float, bottom: Float) {
revealGradientWidth = right - left
@@ -359,8 +389,12 @@
}
override fun onDraw(canvas: Canvas?) {
- if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 ||
- revealAmount == 0f) {
+ if (
+ canvas == null ||
+ revealGradientWidth <= 0 ||
+ revealGradientHeight <= 0 ||
+ revealAmount == 0f
+ ) {
if (revealAmount < 1f) {
canvas?.drawColor(revealGradientEndColor)
}
@@ -383,8 +417,10 @@
}
private fun setPaintColorFilter() {
- gradientPaint.colorFilter = PorterDuffColorFilter(
- getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
- PorterDuff.Mode.MULTIPLY)
+ gradientPaint.colorFilter =
+ PorterDuffColorFilter(
+ getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
+ PorterDuff.Mode.MULTIPLY
+ )
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
similarity index 67%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
index 817c209f..39d7d66 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package com.android.systemui.statusbar;
-import com.android.settingslib.spa.framework.EntryProvider
+import com.android.systemui.shade.NotificationShadeWindowView;
-class GalleryEntryProvider : EntryProvider()
+/**
+ * Calculates insets for the notification shade window view.
+ */
+public abstract class NotificationInsetsController
+ implements NotificationShadeWindowView.LayoutInsetsController {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
new file mode 100644
index 0000000..1ed704e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar;
+
+import static android.view.WindowInsets.Type.systemBars;
+
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Pair;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of NotificationsInsetsController.
+ */
+@SysUISingleton
+public class NotificationInsetsImpl extends NotificationInsetsController {
+
+ @Inject
+ public NotificationInsetsImpl() {
+
+ }
+
+ @Override
+ public Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout) {
+ final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+ int leftInset = 0;
+ int rightInset = 0;
+
+ if (displayCutout != null) {
+ leftInset = displayCutout.getSafeInsetLeft();
+ rightInset = displayCutout.getSafeInsetRight();
+ }
+ leftInset = Math.max(insets.left, leftInset);
+ rightInset = Math.max(insets.right, rightInset);
+
+ return new Pair(leftInset, rightInset);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
new file mode 100644
index 0000000..614bc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface NotificationInsetsModule {
+
+ @Binds
+ @SysUISingleton
+ NotificationInsetsController bindNotificationInsetsController(NotificationInsetsImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 824d3a3..56b689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,7 +31,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 184dc25..cdefae6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,7 +19,6 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.admin.DevicePolicyManager;
@@ -50,6 +49,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -93,6 +93,7 @@
private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
@@ -195,6 +196,7 @@
BroadcastDispatcher broadcastDispatcher,
DevicePolicyManager devicePolicyManager,
UserManager userManager,
+ UserTracker userTracker,
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
NotificationClickNotifier clickNotifier,
@@ -210,7 +212,8 @@
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
mUserManager = userManager;
- mCurrentUserId = ActivityManager.getCurrentUser();
+ mUserTracker = userTracker;
+ mCurrentUserId = mUserTracker.getUserId();
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
mClickNotifier = clickNotifier;
@@ -295,7 +298,7 @@
mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
- mCurrentUserId = ActivityManager.getCurrentUser(); // in case we reg'd receiver too late
+ mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
updateCurrentProfilesCache();
mSettingsObserver.onChange(false); // set up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index e21acb7..0b1807d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -123,9 +123,6 @@
/** Sets whether the window was collapsed by force or not. */
default void setForceWindowCollapsed(boolean force) {}
- /** Sets whether panel is expanded or not. */
- default void setPanelExpanded(boolean isExpanded) {}
-
/** Gets whether the panel is expanded or not. */
default boolean getPanelExpanded() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 815b86e..d7eddf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -71,6 +71,7 @@
private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mStatusBarHeight;
+ private boolean mEnableNotificationClipping;
private AmbientState mAmbientState;
private NotificationStackScrollLayoutController mHostLayoutController;
private int mPaddingBetweenElements;
@@ -117,7 +118,7 @@
// Setting this to first in section to get the clipping to the top roundness correct. This
// value determines the way we are clipping to the top roundness of the overall shade
setFirstInSection(true);
- initDimens();
+ updateResources();
}
public void bind(AmbientState ambientState,
@@ -126,14 +127,17 @@
mHostLayoutController = hostLayoutController;
}
- private void initDimens() {
+ private void updateResources() {
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
- setLayoutParams(layoutParams);
+ final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
+ if (newShelfHeight != layoutParams.height) {
+ layoutParams.height = newShelfHeight;
+ setLayoutParams(layoutParams);
+ }
final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
@@ -141,6 +145,7 @@
mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
mCornerAnimationDistance = res.getDimensionPixelSize(
R.dimen.notification_corner_animation_distance);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
@@ -151,7 +156,7 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- initDimens();
+ updateResources();
}
@Override
@@ -636,7 +641,8 @@
}
if (!isPinned) {
if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
- int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+ int clipBottomAmount =
+ mEnableNotificationClipping ? (int) (viewEnd - notificationClipEnd) : 0;
view.setClipBottomAmount(clipBottomAmount);
} else {
view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index fdec745..58ce447 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -53,6 +53,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.policy.CallbackController;
@@ -152,13 +153,18 @@
private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@Inject
- public StatusBarStateControllerImpl(UiEventLogger uiEventLogger, DumpManager dumpManager,
- InteractionJankMonitor interactionJankMonitor) {
+ public StatusBarStateControllerImpl(
+ UiEventLogger uiEventLogger,
+ DumpManager dumpManager,
+ InteractionJankMonitor interactionJankMonitor,
+ ShadeExpansionStateManager shadeExpansionStateManager
+ ) {
mUiEventLogger = uiEventLogger;
mInteractionJankMonitor = interactionJankMonitor;
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistoricalRecords[i] = new HistoricalState();
}
+ shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
dumpManager.registerDumpable(this);
}
@@ -262,21 +268,6 @@
}
@Override
- public boolean setPanelExpanded(boolean expanded) {
- if (mIsExpanded == expanded) {
- return false;
- }
- mIsExpanded = expanded;
- String tag = getClass().getSimpleName() + "#setIsExpanded";
- DejankUtils.startDetectingBlockingIpcs(tag);
- for (RankedListener rl : new ArrayList<>(mListeners)) {
- rl.mListener.onExpandedChanged(mIsExpanded);
- }
- DejankUtils.stopDetectingBlockingIpcs(tag);
- return true;
- }
-
- @Override
public float getInterpolatedDozeAmount() {
return mDozeInterpolator.getInterpolation(mDozeAmount);
}
@@ -324,6 +315,18 @@
}
}
+ private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+ if (mIsExpanded != isExpanded) {
+ mIsExpanded = isExpanded;
+ String tag = getClass().getSimpleName() + "#setIsExpanded";
+ DejankUtils.startDetectingBlockingIpcs(tag);
+ for (RankedListener rl : new ArrayList<>(mListeners)) {
+ rl.mListener.onExpandedChanged(mIsExpanded);
+ }
+ DejankUtils.stopDetectingBlockingIpcs(tag);
+ }
+ }
+
private void startDozeAnimation() {
if (mDozeAmount == 0f || mDozeAmount == 1f) {
mDozeInterpolator = mIsDozing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 1189107..5a392a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -109,13 +109,6 @@
void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated);
/**
- * Update the expanded state from {@link CentralSurfaces}'s perspective
- * @param expanded are we expanded?
- * @return {@code true} if the state changed, else {@code false}
- */
- boolean setPanelExpanded(boolean expanded);
-
- /**
* Sets whether to leave status bar open when hiding keyguard
*/
void setLeaveOpenOnKeyguardHide(boolean leaveOpen);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index c070fcc..324e972 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -24,17 +24,21 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import javax.inject.Inject;
/**
- *
+ * A Helper class that offloads {@link Vibrator} calls to a different thread.
+ * {@link Vibrator} makes blocking calls that may cause SysUI to ANR.
+ * TODO(b/245528624): Use regular Vibrator instance once new APIs are available.
*/
@SysUISingleton
public class VibratorHelper {
@@ -53,10 +57,18 @@
private final Executor mExecutor;
/**
- *
+ * Creates a vibrator helper on a new single threaded {@link Executor}.
*/
@Inject
- public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
+ public VibratorHelper(@Nullable Vibrator vibrator) {
+ this(vibrator, Executors.newSingleThreadExecutor());
+ }
+
+ /**
+ * Creates new vibrator helper on a specific {@link Executor}.
+ */
+ @VisibleForTesting
+ public VibratorHelper(@Nullable Vibrator vibrator, Executor executor) {
mExecutor = executor;
mVibrator = vibrator;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index ec221b7..c523d22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -15,9 +15,7 @@
*/
package com.android.systemui.statusbar.connectivity;
-import static com.android.settingslib.mobile.MobileMappings.getDefaultIcons;
-import static com.android.settingslib.mobile.MobileMappings.getIconKey;
-import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
import android.content.Context;
import android.content.Intent;
@@ -46,6 +44,7 @@
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.SignalStrengthUtil;
import com.android.systemui.R;
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy;
import com.android.systemui.util.CarrierConfigTracker;
import java.io.PrintWriter;
@@ -63,6 +62,7 @@
private final TelephonyManager mPhone;
private final CarrierConfigTracker mCarrierConfigTracker;
private final SubscriptionDefaults mDefaults;
+ private final MobileMappingsProxy mMobileMappingsProxy;
private final String mNetworkNameDefault;
private final String mNetworkNameSeparator;
private final ContentObserver mObserver;
@@ -121,6 +121,7 @@
TelephonyManager phone,
CallbackHandler callbackHandler,
NetworkControllerImpl networkController,
+ MobileMappingsProxy mobileMappingsProxy,
SubscriptionInfo info,
SubscriptionDefaults defaults,
Looper receiverLooper,
@@ -135,13 +136,14 @@
mPhone = phone;
mDefaults = defaults;
mSubscriptionInfo = info;
+ mMobileMappingsProxy = mobileMappingsProxy;
mNetworkNameSeparator = getTextIfExists(
R.string.status_bar_network_name_separator).toString();
mNetworkNameDefault = getTextIfExists(
com.android.internal.R.string.lockscreen_carrier_default).toString();
- mNetworkToIconLookup = mapIconSets(mConfig);
- mDefaultIcons = getDefaultIcons(mConfig);
+ mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig);
+ mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig);
String networkName = info.getCarrierName() != null ? info.getCarrierName().toString()
: mNetworkNameDefault;
@@ -161,8 +163,8 @@
void setConfiguration(Config config) {
mConfig = config;
updateInflateSignalStrength();
- mNetworkToIconLookup = mapIconSets(mConfig);
- mDefaultIcons = getDefaultIcons(mConfig);
+ mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig);
+ mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig);
updateTelephony();
}
@@ -271,8 +273,9 @@
dataContentDescription = mContext.getString(R.string.data_connection_no_internet);
}
- final QsInfo qsInfo = getQsInfo(contentDescription, icons.dataType);
- final SbInfo sbInfo = getSbInfo(contentDescription, icons.dataType);
+ int iconId = mCurrentState.getNetworkTypeIcon(mContext);
+ final QsInfo qsInfo = getQsInfo(contentDescription, iconId);
+ final SbInfo sbInfo = getSbInfo(contentDescription, iconId);
MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
sbInfo.icon,
@@ -373,6 +376,10 @@
} else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
updateDataSim();
notifyListenersIfNecessary();
+ } else if (action.equals(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) {
+ int carrierId = intent.getIntExtra(
+ TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID);
+ mCurrentState.setCarrierId(carrierId);
}
}
@@ -477,7 +484,8 @@
mCurrentState.level = getSignalLevel(mCurrentState.signalStrength);
}
- String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo);
+ mCurrentState.setCarrierId(mPhone.getSimCarrierId());
+ String iconKey = mMobileMappingsProxy.getIconKey(mCurrentState.telephonyDisplayInfo);
if (mNetworkToIconLookup.get(iconKey) != null) {
mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
index 7938179..a323454 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
@@ -22,6 +22,7 @@
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileStatusTracker
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
@@ -33,6 +34,7 @@
val context: Context,
val callbackHandler: CallbackHandler,
val carrierConfigTracker: CarrierConfigTracker,
+ val mobileMappings: MobileMappingsProxy,
) {
fun createMobileSignalController(
config: MobileMappings.Config,
@@ -56,6 +58,7 @@
phone,
callbackHandler,
networkController,
+ mobileMappings,
subscriptionInfo,
subscriptionDefaults,
receiverLooper,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index f20d206..1fb6a98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.connectivity
+import android.annotation.DrawableRes
+import android.content.Context
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.Utils
import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus
import com.android.settingslib.mobile.TelephonyIcons
@@ -41,7 +45,7 @@
@JvmField var roaming: Boolean = false,
@JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED,
// Tracks the on/off state of the defaultDataSubscription
- @JvmField var defaultDataOff: Boolean = false
+ @JvmField var defaultDataOff: Boolean = false,
) : ConnectivityState() {
@JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
@@ -49,6 +53,11 @@
@JvmField var serviceState: ServiceState? = null
@JvmField var signalStrength: SignalStrength? = null
+ var carrierId = TelephonyManager.UNKNOWN_CARRIER_ID
+
+ @VisibleForTesting
+ var networkTypeResIdCache: NetworkTypeResIdCache = NetworkTypeResIdCache()
+
/** @return true if this state is disabled or not default data */
val isDataDisabledOrNotDefault: Boolean
get() = (iconGroup === TelephonyIcons.DATA_DISABLED ||
@@ -125,6 +134,21 @@
return serviceState != null && serviceState!!.roaming
}
+ /**
+ *
+ * Load the (potentially customized) icon resource id for the current network type. Note that
+ * this operation caches the result. Note that reading the [MobileIconGroup.dataType] field
+ * directly will not yield correct results in cases where the carrierId has an associated
+ * override. This is the preferred method for getting the network type indicator.
+ *
+ * @return a drawable res id appropriate for the current (carrierId, networkType) pair
+ */
+ @DrawableRes
+ fun getNetworkTypeIcon(context: Context): Int {
+ val icon = (iconGroup as MobileIconGroup)
+ return networkTypeResIdCache.get(icon, carrierId, context)
+ }
+
fun setFromMobileStatus(mobileStatus: MobileStatus) {
activityIn = mobileStatus.activityIn
activityOut = mobileStatus.activityOut
@@ -140,6 +164,7 @@
super.toString(builder)
builder.append(',')
builder.append("dataSim=$dataSim,")
+ builder.append("carrierId=$carrierId")
builder.append("networkName=$networkName,")
builder.append("networkNameData=$networkNameData,")
builder.append("dataConnected=$dataConnected,")
@@ -157,6 +182,8 @@
builder.append("voiceServiceState=${getVoiceServiceState()},")
builder.append("isInService=${isInService()},")
+ builder.append("networkTypeIconCache=$networkTypeResIdCache")
+
builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},")
builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},")
builder.append("displayInfo=$telephonyDisplayInfo")
@@ -164,6 +191,7 @@
override fun tableColumns(): List<String> {
val columns = listOf("dataSim",
+ "carrierId",
"networkName",
"networkNameData",
"dataConnected",
@@ -178,6 +206,7 @@
"showQuickSettingsRatIcon",
"voiceServiceState",
"isInService",
+ "networkTypeIconCache",
"serviceState",
"signalStrength",
"displayInfo")
@@ -187,6 +216,7 @@
override fun tableData(): List<String> {
val columns = listOf(dataSim,
+ carrierId,
networkName,
networkNameData,
dataConnected,
@@ -201,6 +231,7 @@
showQuickSettingsRatIcon(),
getVoiceServiceState(),
isInService(),
+ networkTypeResIdCache,
serviceState?.minLog() ?: "(null)",
signalStrength?.minLog() ?: "(null)",
telephonyDisplayInfo).map { it.toString() }
@@ -217,6 +248,7 @@
if (networkName != other.networkName) return false
if (networkNameData != other.networkNameData) return false
+ if (carrierId != other.carrierId) return false
if (dataSim != other.dataSim) return false
if (dataConnected != other.dataConnected) return false
if (isEmergency != other.isEmergency) return false
@@ -238,6 +270,7 @@
var result = super.hashCode()
result = 31 * result + (networkName?.hashCode() ?: 0)
result = 31 * result + (networkNameData?.hashCode() ?: 0)
+ result = 31 * result + (carrierId.hashCode())
result = 31 * result + dataSim.hashCode()
result = 31 * result + dataConnected.hashCode()
result = 31 * result + isEmergency.hashCode()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 450b757..99ff06a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -22,6 +22,7 @@
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -38,6 +39,7 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -75,7 +77,7 @@
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -127,7 +129,7 @@
private final boolean mHasMobileDataFeature;
private final SubscriptionDefaults mSubDefaults;
private final DataSaverController mDataSaverController;
- private final CurrentUserTracker mUserTracker;
+ private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DemoModeController mDemoModeController;
private final Object mLock = new Object();
@@ -138,7 +140,7 @@
private final MobileSignalControllerFactory mMobileFactory;
private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
- private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private int mActiveMobileDataSubscription = INVALID_SUBSCRIPTION_ID;
// Subcontrollers.
@VisibleForTesting
@@ -211,6 +213,14 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ NetworkControllerImpl.this.onUserSwitched(newUser);
+ }
+ };
+
/**
* Construct this controller object and register for updates.
*/
@@ -223,6 +233,7 @@
CallbackHandler callbackHandler,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ConnectivityManager connectivityManager,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
@@ -250,6 +261,7 @@
new SubscriptionDefaults(),
deviceProvisionedController,
broadcastDispatcher,
+ userTracker,
demoModeController,
carrierConfigTracker,
trackerFactory,
@@ -276,6 +288,7 @@
SubscriptionDefaults defaultsHandler,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
WifiStatusTrackerFactory trackerFactory,
@@ -332,13 +345,9 @@
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- NetworkControllerImpl.this.onUserSwitched(newUserId);
- }
- };
- mUserTracker.startTracking();
+ mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
@@ -502,6 +511,7 @@
filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+ filter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
@@ -792,6 +802,20 @@
mConfig = Config.readConfig(mContext);
mReceiverHandler.post(this::handleConfigurationChanged);
break;
+
+ case TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED: {
+ // Notify the relevant MobileSignalController of the change
+ int subId = intent.getIntExtra(
+ TelephonyManager.EXTRA_SUBSCRIPTION_ID,
+ INVALID_SUBSCRIPTION_ID
+ );
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
+ mMobileSignalControllers.get(subId).handleBroadcast(intent);
+ }
+ }
+ }
+ break;
case Intent.ACTION_SIM_STATE_CHANGED:
// Avoid rebroadcast because SysUI is direct boot aware.
if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
@@ -819,7 +843,7 @@
break;
default:
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
mMobileSignalControllers.get(subId).handleBroadcast(intent);
@@ -1335,6 +1359,9 @@
String slotString = args.getString("slot");
int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
slot = MathUtils.constrain(slot, 0, 8);
+ String carrierIdString = args.getString("carrierid");
+ int carrierId = TextUtils.isEmpty(carrierIdString) ? 0
+ : Integer.parseInt(carrierIdString);
// Ensure we have enough sim slots
List<SubscriptionInfo> subs = new ArrayList<>();
while (mMobileSignalControllers.size() <= slot) {
@@ -1346,6 +1373,9 @@
}
// Hack to index linearly for easy use.
MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
+ if (carrierId != 0) {
+ controller.getState().setCarrierId(carrierId);
+ }
controller.getState().dataSim = datatype != null;
controller.getState().isDefault = datatype != null;
controller.getState().dataConnected = datatype != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt
new file mode 100644
index 0000000..9be7ee9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.statusbar.connectivity
+
+import android.annotation.DrawableRes
+import android.content.Context
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
+
+/**
+ * Cache for network type resource IDs.
+ *
+ * The default framework behavior is to have a statically defined icon per network type. See
+ * [MobileIconGroup] for the standard mapping.
+ *
+ * For the case of carrierId-defined overrides, we want to check [MobileIconCarrierIdOverrides] for
+ * an existing icon override, and cache the result of the operation
+ */
+class NetworkTypeResIdCache(
+ private val overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+) {
+ @DrawableRes private var cachedResId: Int = 0
+ private var lastCarrierId: Int? = null
+ private var lastIconGroup: MobileIconGroup? = null
+ private var isOverridden: Boolean = false
+
+ @DrawableRes
+ fun get(iconGroup: MobileIconGroup, carrierId: Int, context: Context): Int {
+ if (lastCarrierId != carrierId || lastIconGroup != iconGroup) {
+ lastCarrierId = carrierId
+ lastIconGroup = iconGroup
+
+ val maybeOverride = calculateOverriddenIcon(iconGroup, carrierId, context)
+ if (maybeOverride > 0) {
+ cachedResId = maybeOverride
+ isOverridden = true
+ } else {
+ cachedResId = iconGroup.dataType
+ isOverridden = false
+ }
+ }
+
+ return cachedResId
+ }
+
+ override fun toString(): String {
+ return "networkTypeResIdCache={id=$cachedResId, isOverridden=$isOverridden}"
+ }
+
+ @DrawableRes
+ private fun calculateOverriddenIcon(
+ iconGroup: MobileIconGroup,
+ carrierId: Int,
+ context: Context,
+ ): Int {
+ val name = iconGroup.name
+ if (!overrides.carrierIdEntryExists(carrierId)) {
+ return 0
+ }
+
+ return overrides.getOverrideFor(carrierId, name, context.resources)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
index a02dd34..42b874f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
@@ -37,6 +37,13 @@
* own [Configuration] and track resources based on the full set of available mcc-mnc combinations.
*
* (for future reference: b/240555502 is the initiating bug for this)
+ *
+ * NOTE: MCC/MNC qualifiers are not sufficient to fully describe a network type icon qualified by
+ * network type + carrier ID. This class exists to keep the legacy behavior of using the MCC/MNC
+ * resource qualifiers working, but if a carrier-specific icon is requested, then the override
+ * provided by [MobileIconCarrierIdOverrides] will take precedence.
+ *
+ * TODO(b/258503704): consider removing this class in favor of the `carrierId` overrides
*/
@SysUISingleton
class MobileContextProvider
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 eacb18e..14d0d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -300,7 +300,7 @@
@Override
public boolean isShowingAlternateAuthOnUnlock() {
- return statusBarKeyguardViewManager.get().shouldShowAltAuth();
+ return statusBarKeyguardViewManager.get().canShowAlternateBouncer();
}
};
return new DialogLaunchAnimator(callback, interactionJankMonitor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 5873837..6bd9502 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -235,19 +235,24 @@
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
- activityStarter.startActivity(
- intent,
- true, /* dismissShade */
- null, /* launch animator - looks bad with the transparent smartspace bg */
- showOnLockscreen
- )
+ if (showOnLockscreen) {
+ activityStarter.startActivity(
+ intent,
+ true, /* dismissShade */
+ // launch animator - looks bad with the transparent smartspace bg
+ null,
+ true
+ )
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+ }
}
override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
if (showOnLockscreen) {
pi.send()
} else {
- activityStarter.startPendingIntentDismissingKeyguard(pi)
+ activityStarter.postStartActivityDismissingKeyguard(pi)
}
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 2734511..7eb8906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -40,4 +40,8 @@
val isSemiStableSortEnabled: Boolean by lazy {
featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
}
+
+ val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
+ featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+ }
}
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 585d871..37d82ca 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
@@ -676,9 +676,6 @@
return row != null && row.areChildrenExpanded();
}
- public boolean keepInParent() {
- return row != null && row.keepInParent();
- }
//TODO: probably less confusing to say "is group fully visible"
public boolean isGroupNotFullyVisible() {
@@ -698,10 +695,6 @@
return row != null && row.isSummaryWithChildren();
}
- public void setKeepInParent(boolean keep) {
- if (row != null) row.setKeepInParent(keep);
- }
-
public void onDensityOrFontScaleChanged() {
if (row != null) row.onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
deleted file mode 100644
index e3d71c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-
-import javax.inject.Inject;
-
-/**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
- */
-@CoordinatorScope
-public class KeyguardCoordinator implements Coordinator {
- private static final String TAG = "KeyguardCoordinator";
- private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
- private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
- private final StatusBarStateController mStatusBarStateController;
-
- @Inject
- public KeyguardCoordinator(
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
- StatusBarStateController statusBarStateController) {
- mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
- mStatusBarStateController = statusBarStateController;
- }
-
- @Override
- public void attach(NotifPipeline pipeline) {
-
- setupInvalidateNotifListCallbacks();
- // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
- pipeline.addFinalizeFilter(mNotifFilter);
- mKeyguardNotificationVisibilityProvider
- .addOnStateChangedListener(this::invalidateListFromFilter);
- updateSectionHeadersVisibility();
- }
-
- private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
- @Override
- public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
- return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
- }
- };
-
- // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
- // these same updates
- private void setupInvalidateNotifListCallbacks() {
-
- }
-
- private void invalidateListFromFilter(String reason) {
- updateSectionHeadersVisibility();
- mNotifFilter.invalidateList(reason);
- }
-
- private void updateSectionHeadersVisibility() {
- boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
- boolean showSections = !onKeyguard && !neverShowSections;
- mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
- }
-}
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
new file mode 100644
index 0000000..6e5fceb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+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.interruption.KeyguardNotificationVisibilityProvider
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen.
+ */
+@CoordinatorScope
+class KeyguardCoordinator
+@Inject
+constructor(
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+ private val keyguardRepository: KeyguardRepository,
+ private val notifPipelineFlags: NotifPipelineFlags,
+ @Application private val scope: CoroutineScope,
+ private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+ private val statusBarStateController: StatusBarStateController,
+) : Coordinator {
+
+ private val unseenNotifications = mutableSetOf<NotificationEntry>()
+
+ override fun attach(pipeline: NotifPipeline) {
+ setupInvalidateNotifListCallbacks()
+ // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
+ pipeline.addFinalizeFilter(notifFilter)
+ keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter)
+ updateSectionHeadersVisibility()
+ if (notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard) {
+ attachUnseenFilter(pipeline)
+ }
+ }
+
+ private fun attachUnseenFilter(pipeline: NotifPipeline) {
+ pipeline.addFinalizeFilter(unseenNotifFilter)
+ pipeline.addCollectionListener(collectionListener)
+ scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+ }
+
+ private suspend fun clearUnseenWhenKeyguardIsDismissed() {
+ // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
+ // during the timeout period
+ keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
+ if (!isKeyguardShowing) {
+ unseenNotifFilter.invalidateList("keyguard no longer showing")
+ delay(SEEN_TIMEOUT)
+ unseenNotifications.clear()
+ }
+ }
+ }
+
+ private val collectionListener =
+ object : NotifCollectionListener {
+ override fun onEntryAdded(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryUpdated(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ unseenNotifications.remove(entry)
+ }
+ }
+
+ @VisibleForTesting
+ internal val unseenNotifFilter =
+ object : NotifFilter("$TAG-unseen") {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ when {
+ // Don't apply filter if the keyguard isn't currently showing
+ !keyguardRepository.isKeyguardShowing() -> false
+ // Don't apply the filter if the notification is unseen
+ unseenNotifications.contains(entry) -> false
+ // Don't apply the filter to (non-promoted) group summaries
+ // - summary will be pruned if necessary, depending on if children are filtered
+ entry.parent?.summary == entry -> false
+ else -> true
+ }
+ }
+
+ private val notifFilter: NotifFilter =
+ object : NotifFilter(TAG) {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
+ }
+
+ // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+ // these same updates
+ private fun setupInvalidateNotifListCallbacks() {}
+
+ private fun invalidateListFromFilter(reason: String) {
+ updateSectionHeadersVisibility()
+ notifFilter.invalidateList(reason)
+ }
+
+ private fun updateSectionHeadersVisibility() {
+ val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
+ val neverShowSections = sectionHeaderVisibilityProvider.neverShowSectionHeaders
+ val showSections = !onKeyguard && !neverShowSections
+ sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
+ }
+
+ companion object {
+ private const val TAG = "KeyguardCoordinator"
+ private val SEEN_TIMEOUT = 5.seconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 3002a68..a2379b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -62,6 +63,7 @@
private final HeadsUpManager mHeadsUpManager;
private final ShadeStateEvents mShadeStateEvents;
private final StatusBarStateController mStatusBarStateController;
+ private final VisibilityLocationProvider mVisibilityLocationProvider;
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -94,9 +96,11 @@
HeadsUpManager headsUpManager,
ShadeStateEvents shadeStateEvents,
StatusBarStateController statusBarStateController,
+ VisibilityLocationProvider visibilityLocationProvider,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle) {
mHeadsUpManager = headsUpManager;
+ mVisibilityLocationProvider = visibilityLocationProvider;
mVisualStabilityProvider = visualStabilityProvider;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
@@ -123,6 +127,11 @@
// HUNs to the top of the shade
private final NotifStabilityManager mNotifStabilityManager =
new NotifStabilityManager("VisualStabilityCoordinator") {
+ private boolean canMoveForHeadsUp(NotificationEntry entry) {
+ return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+ && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+ }
+
@Override
public void onBeginRun() {
mIsSuppressingPipelineRun = false;
@@ -140,7 +149,7 @@
@Override
public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isGroupChangeAllowedForEntry =
- mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+ mReorderingAllowed || canMoveForHeadsUp(entry);
mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
return isGroupChangeAllowedForEntry;
}
@@ -156,7 +165,7 @@
public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isSectionChangeAllowedForEntry =
mReorderingAllowed
- || mHeadsUpManager.isAlerting(entry.getKey())
+ || canMoveForHeadsUp(entry)
|| mEntriesThatCanChangeSection.containsKey(entry.getKey());
if (!isSectionChangeAllowedForEntry) {
mEntriesWithSuppressedSectionChange.add(entry.getKey());
@@ -165,8 +174,8 @@
}
@Override
- public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
- return mReorderingAllowed;
+ public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+ return mReorderingAllowed || canMoveForHeadsUp(entry.getRepresentativeEntry());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
new file mode 100644
index 0000000..4bc4ecf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.statusbar.notification.collection.provider
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * An injectable component which delegates the visibility location computation to a delegate which
+ * can be initialized after the initial injection, generally because it's provided by a view.
+ */
+@SysUISingleton
+class VisibilityLocationProviderDelegator @Inject constructor() : VisibilityLocationProvider {
+ private var delegate: VisibilityLocationProvider? = null
+
+ fun setDelegate(provider: VisibilityLocationProvider) {
+ delegate = provider
+ }
+
+ override fun isInVisibleLocation(entry: NotificationEntry): Boolean =
+ requireNotNull(this.delegate) { "delegate not initialized" }.isInVisibleLocation(entry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
index f949af0..8de0381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
@@ -55,4 +55,8 @@
override val view: View
get() = mediaContainerView!!
+
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 26ba12c..ae72a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -30,6 +30,7 @@
* below.
*/
interface NodeController {
+
/** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
val nodeLabel: String
@@ -64,6 +65,27 @@
/** Called when this view has been removed */
fun onViewRemoved() {}
+
+ /**
+ * Called before removing a node from its parent
+ *
+ * If returned true, the ShadeViewDiffer won't detach this row and the view system is
+ * responsible for ensuring the row is in eventually removed from the parent.
+ *
+ * @return false to opt out from this feature
+ */
+ fun offerToKeepInParentForAnimation(): Boolean
+
+ /**
+ * Called before a node is reattached. Removes the view from its parent
+ * if it was flagged to be kept before.
+ *
+ * @return whether it did a removal
+ */
+ fun removeFromParentIfKeptForAnimation(): Boolean
+
+ /** Called when a node is being reattached */
+ fun resetKeepInParentForAnimation()
}
/**
@@ -90,7 +112,7 @@
}
private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
- sb.append("${indent}{${tree.controller.nodeLabel}}\n")
+ sb.append("$indent{${tree.controller.nodeLabel}}\n")
if (tree.children.isNotEmpty()) {
val childIndent = "$indent "
for (child in tree.children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index 2073e92..5ff686a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -32,6 +32,9 @@
override val view: View
) : NodeController, PipelineDumpable {
override val nodeLabel: String = "<root>"
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
override fun getChildAt(index: Int): View? {
return listContainer.getContainerChildAt(index)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 2c9508e..7b59266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -100,4 +100,7 @@
override val view: View
get() = _view!!
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 9a9941e..18ee481 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -86,10 +86,10 @@
}
private fun maybeDetachChild(
- parentNode: ShadeNode,
- parentSpec: NodeSpec?,
- childNode: ShadeNode,
- childSpec: NodeSpec?
+ parentNode: ShadeNode,
+ parentSpec: NodeSpec?,
+ childNode: ShadeNode,
+ childSpec: NodeSpec?
) {
val newParentNode = childSpec?.parent?.let { getNode(it) }
@@ -100,14 +100,27 @@
nodes.remove(childNode.controller)
}
- logger.logDetachingChild(
- key = childNode.label,
- isTransfer = !childCompletelyRemoved,
- isParentRemoved = parentSpec == null,
- oldParent = parentNode.label,
- newParent = newParentNode?.label)
- parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
- childNode.parent = null
+ if (childCompletelyRemoved && parentSpec == null &&
+ childNode.offerToKeepInParentForAnimation()) {
+ // If both the child and the parent are being removed at the same time, then
+ // keep the child attached to the parent for animation purposes
+ logger.logSkipDetachingChild(
+ key = childNode.label,
+ parentKey = parentNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = true
+ )
+ } else {
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = parentSpec == null,
+ oldParent = parentNode.label,
+ newParent = newParentNode?.label
+ )
+ parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+ childNode.parent = null
+ }
}
}
@@ -119,6 +132,16 @@
val childNode = getNode(childSpec)
if (childNode.view != currView) {
+ val removedFromParent = childNode.removeFromParentIfKeptForAnimation()
+ if (removedFromParent) {
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = false,
+ isParentRemoved = true,
+ oldParent = null,
+ newParent = null
+ )
+ }
when (childNode.parent) {
null -> {
@@ -142,6 +165,8 @@
}
}
+ childNode.resetKeepInParentForAnimation()
+
if (childSpec.children.isNotEmpty()) {
attachChildren(childNode, specMap)
}
@@ -213,4 +238,16 @@
controller.removeChild(child.controller, isTransfer)
child.controller.onViewRemoved()
}
+
+ fun offerToKeepInParentForAnimation(): Boolean {
+ return controller.offerToKeepInParentForAnimation()
+ }
+
+ fun removeFromParentIfKeptForAnimation(): Boolean {
+ return controller.removeFromParentIfKeptForAnimation()
+ }
+
+ fun resetKeepInParentForAnimation() {
+ controller.resetKeepInParentForAnimation()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index b4b9438..1e22c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -43,6 +43,20 @@
})
}
+ fun logSkipDetachingChild(
+ key: String,
+ parentKey: String?,
+ isTransfer: Boolean,
+ isParentRemoved: Boolean
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ str2 = parentKey
+ bool1 = isTransfer
+ bool2 = isParentRemoved
+ }, { "Skip detaching $str1 from $str2 isTransfer=$bool1 isParentRemoved=$bool2" })
+ }
+
fun logAttachingChild(key: String, parent: String, atIndex: Int) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = key
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 ff63891..a7b7a23 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
@@ -34,8 +34,10 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeEventsModule;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
@@ -50,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -150,6 +153,11 @@
@Binds
NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
+ /** Provides an instance of {@link VisibilityLocationProvider} */
+ @Binds
+ VisibilityLocationProvider bindVisibilityLocationProvider(
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
+
/** Provides an instance of {@link NotificationLogger} */
@SysUISingleton
@Provides
@@ -160,6 +168,7 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
+ ShadeExpansionStateManager shadeExpansionStateManager,
NotificationLogger.ExpansionStateLogger expansionStateLogger,
NotificationPanelLogger notificationPanelLogger) {
return new NotificationLogger(
@@ -169,6 +178,7 @@
visibilityProvider,
notifPipeline,
statusBarStateController,
+ shadeExpansionStateManager,
expansionStateLogger,
notificationPanelLogger);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 6391877..5f6a5cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -36,6 +36,7 @@
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -52,6 +53,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -92,25 +94,6 @@
private Boolean mPanelExpanded = null; // Use null to indicate state is not yet known
private boolean mLogging = false;
- protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
- new OnChildLocationsChangedListener() {
- @Override
- public void onChildLocationsChanged() {
- if (mHandler.hasCallbacks(mVisibilityReporter)) {
- // Visibilities will be reported when the existing
- // callback is executed.
- return;
- }
- // Calculate when we're allowed to run the visibility
- // reporter. Note that this timestamp might already have
- // passed. That's OK, the callback will just be executed
- // ASAP.
- long nextReportUptimeMs =
- mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
- mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
- }
- };
-
// Tracks notifications currently visible in mNotificationStackScroller and
// emits visibility events via NoMan on changes.
protected Runnable mVisibilityReporter = new Runnable() {
@@ -219,6 +202,7 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
+ ShadeExpansionStateManager shadeExpansionStateManager,
ExpansionStateLogger expansionStateLogger,
NotificationPanelLogger notificationPanelLogger) {
mNotificationListener = notificationListener;
@@ -232,6 +216,7 @@
mNotificationPanelLogger = notificationPanelLogger;
// Not expected to be destroyed, don't need to unsubscribe
statusBarStateController.addCallback(this);
+ shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
registerNewPipelineListener();
}
@@ -278,14 +263,14 @@
if (DEBUG) {
Log.i(TAG, "startNotificationLogging");
}
- mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+ mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
// Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
// cause the scroller to emit child location events. Hence generate
// one ourselves to guarantee that we're reporting visible
// notifications.
// (Note that in cases where the scroller does emit events, this
// additional event doesn't break anything.)
- mNotificationLocationsChangedListener.onChildLocationsChanged();
+ onChildLocationsChanged();
}
}
@@ -411,21 +396,6 @@
}
/**
- * Called by CentralSurfaces to notify the logger that the panel expansion has changed.
- * The panel may be showing any of the normal notification panel, the AOD, or the bouncer.
- * @param isExpanded True if the panel is expanded.
- */
- public void onPanelExpandedChanged(boolean isExpanded) {
- if (DEBUG) {
- Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
- }
- mPanelExpanded = isExpanded;
- synchronized (mDozingLock) {
- maybeUpdateLoggingStatus();
- }
- }
-
- /**
* Called when the notification is expanded / collapsed.
*/
public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
@@ -434,6 +404,36 @@
}
@VisibleForTesting
+ void onShadeExpansionFullyChanged(Boolean isExpanded) {
+ // mPanelExpanded is initialized as null
+ if (mPanelExpanded == null || !mPanelExpanded.equals(isExpanded)) {
+ if (DEBUG) {
+ Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
+ }
+ mPanelExpanded = isExpanded;
+ synchronized (mDozingLock) {
+ maybeUpdateLoggingStatus();
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void onChildLocationsChanged() {
+ if (mHandler.hasCallbacks(mVisibilityReporter)) {
+ // Visibilities will be reported when the existing
+ // callback is executed.
+ return;
+ }
+ // Calculate when we're allowed to run the visibility
+ // reporter. Note that this timestamp might already have
+ // passed. That's OK, the callback will just be executed
+ // ASAP.
+ long nextReportUptimeMs =
+ mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
+ mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
+ }
+
+ @VisibleForTesting
public void setVisibilityReporter(Runnable visibilityReporter) {
mVisibilityReporter = visibilityReporter;
}
@@ -535,7 +535,7 @@
return;
}
if (loggedExpansionState != null
- && state.mIsExpanded == loggedExpansionState) {
+ && Objects.equals(state.mIsExpanded, loggedExpansionState)) {
return;
}
mLoggedExpansionState.put(key, state.mIsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index de158c4..1eccc98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -135,6 +135,8 @@
private static final String TAG = "ExpandableNotifRow";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG_ONMEASURE =
+ Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
private static final int COLORED_DIVIDER_ALPHA = 0x7B;
private static final int MENU_VIEW_INDEX = 0;
@@ -238,7 +240,7 @@
private NotificationContentView mPrivateLayout;
private NotificationContentView[] mLayouts;
private int mNotificationColor;
- private ExpansionLogger mLogger;
+ private ExpandableNotificationRowLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
private NotificationEntry mEntry;
@@ -337,7 +339,7 @@
}
}
};
- private boolean mKeepInParent;
+ private boolean mKeepInParentForDismissAnimation;
private boolean mRemoved;
private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
new FloatProperty<ExpandableNotificationRow>("translate") {
@@ -823,6 +825,12 @@
if (mChildrenContainer == null) {
mChildrenContainerStub.inflate();
}
+
+ if (row.keepInParentForDismissAnimation()) {
+ logSkipAttachingKeepInParentChild(row);
+ return;
+ }
+
mChildrenContainer.addNotification(row, childIndex);
onAttachedChildrenCountChanged();
row.setIsChildInGroup(true, this);
@@ -831,6 +839,7 @@
public void removeChildNotification(ExpandableNotificationRow row) {
if (mChildrenContainer != null) {
mChildrenContainer.removeNotification(row);
+ row.setKeepInParentForDismissAnimation(false);
}
onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null);
@@ -838,6 +847,31 @@
}
/**
+ * Removes the children notifications which were marked to keep for the dismissal animation.
+ */
+ public void removeChildrenWithKeepInParent() {
+ if (mChildrenContainer == null) return;
+
+ List<ExpandableNotificationRow> clonedList = new ArrayList<>(
+ mChildrenContainer.getAttachedChildren());
+ boolean childCountChanged = false;
+ for (ExpandableNotificationRow child : clonedList) {
+ if (child.keepInParentForDismissAnimation()) {
+ mChildrenContainer.removeNotification(child);
+ child.setIsChildInGroup(false, null);
+ child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+ child.setKeepInParentForDismissAnimation(false);
+ logKeepInParentChildDetached(child);
+ childCountChanged = true;
+ }
+ }
+
+ if (childCountChanged) {
+ onAttachedChildrenCountChanged();
+ }
+ }
+
+ /**
* Returns the child notification at [index], or null if no such child.
*/
@Nullable
@@ -1359,12 +1393,15 @@
}
}
- public boolean keepInParent() {
- return mKeepInParent;
+ /**
+ * @return if this entry should be kept in its parent during removal.
+ */
+ public boolean keepInParentForDismissAnimation() {
+ return mKeepInParentForDismissAnimation;
}
- public void setKeepInParent(boolean keepInParent) {
- mKeepInParent = keepInParent;
+ public void setKeepInParentForDismissAnimation(boolean keepInParent) {
+ mKeepInParentForDismissAnimation = keepInParent;
}
@Override
@@ -1511,7 +1548,7 @@
l.setAlpha(alpha);
}
if (mChildrenContainer != null) {
- mChildrenContainer.setAlpha(alpha);
+ mChildrenContainer.setContentAlpha(alpha);
}
}
@@ -1535,8 +1572,29 @@
mUseIncreasedHeadsUpHeight = use;
}
- public interface ExpansionLogger {
+ /**
+ * Interface for logging {{@link ExpandableNotificationRow} events.}
+ */
+ public interface ExpandableNotificationRowLogger {
+ /**
+ * Called when the notification is expanded / collapsed.
+ */
void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+
+ /**
+ * Called when a notification which was previously kept in its parent for the
+ * dismiss animation is finally detached from its parent.
+ */
+ void logKeepInParentChildDetached(NotificationEntry child, NotificationEntry oldParent);
+
+ /**
+ * Called when we want to attach a notification to a new parent,
+ * but it still has the keepInParent flag set, so we skip it.
+ */
+ void logSkipAttachingKeepInParentChild(
+ NotificationEntry child,
+ NotificationEntry newParent
+ );
}
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
@@ -1554,7 +1612,7 @@
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
String appName,
String notificationKey,
- ExpansionLogger logger,
+ ExpandableNotificationRowLogger logger,
KeyguardBypassController bypassController,
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
@@ -1724,6 +1782,11 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+ if (DEBUG_ONMEASURE) {
+ Log.d(TAG, "onMeasure("
+ + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Trace.endSection();
}
@@ -3560,6 +3623,18 @@
});
}
+ private void logKeepInParentChildDetached(ExpandableNotificationRow child) {
+ if (mLogger != null) {
+ mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry());
+ }
+ }
+
+ private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) {
+ if (mLogger != null) {
+ mLogger.logSkipAttachingKeepInParentChild(child.getEntry(), getEntry());
+ }
+ }
+
private void setTargetPoint(Point p) {
mTargetPoint = p;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 842526e..f9e9a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -33,9 +33,9 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -83,13 +83,11 @@
private final GroupExpansionManager mGroupExpansionManager;
private final RowContentBindStage mRowContentBindStage;
private final NotificationLogger mNotificationLogger;
+ private final NotificationRowLogger mLogBufferLogger;
private final HeadsUpManager mHeadsUpManager;
private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
private final StatusBarStateController mStatusBarStateController;
private final MetricsLogger mMetricsLogger;
-
- private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
- this::logNotificationExpansion;
private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
private final NotificationGutsManager mNotificationGutsManager;
private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -101,8 +99,32 @@
private final Optional<BubblesManager> mBubblesManagerOptional;
private final SmartReplyConstants mSmartReplyConstants;
private final SmartReplyController mSmartReplyController;
-
private final ExpandableNotificationRowDragController mDragController;
+ private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
+ new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
+ @Override
+ public void logNotificationExpansion(String key, boolean userAction,
+ boolean expanded) {
+ mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+ }
+
+ @Override
+ public void logKeepInParentChildDetached(
+ NotificationEntry child,
+ NotificationEntry oldParent
+ ) {
+ mLogBufferLogger.logKeepInParentChildDetached(child, oldParent);
+ }
+
+ @Override
+ public void logSkipAttachingKeepInParentChild(
+ NotificationEntry child,
+ NotificationEntry newParent
+ ) {
+ mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
+ }
+ };
+
@Inject
public ExpandableNotificationRowController(
@@ -110,6 +132,7 @@
ActivatableNotificationViewController activatableNotificationViewController,
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
MetricsLogger metricsLogger,
+ NotificationRowLogger logBufferLogger,
NotificationListContainer listContainer,
NotificationMediaManager mediaManager,
SmartReplyConstants smartReplyConstants,
@@ -163,6 +186,7 @@
mBubblesManagerOptional = bubblesManagerOptional;
mDragController = dragController;
mMetricsLogger = metricsLogger;
+ mLogBufferLogger = logBufferLogger;
mSmartReplyConstants = smartReplyConstants;
mSmartReplyController = smartReplyController;
}
@@ -177,7 +201,7 @@
mRemoteInputViewSubcomponentFactory,
mAppName,
mNotificationKey,
- mExpansionLogger,
+ mLoggerCallback,
mKeyguardBypassController,
mGroupMembershipManager,
mGroupExpansionManager,
@@ -243,10 +267,6 @@
}
};
- private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mNotificationLogger.onExpansionChanged(key, userAction, expanded);
- }
-
@Override
@NonNull
public String getNodeLabel() {
@@ -336,4 +356,29 @@
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
mView.setFeedbackIcon(icon);
}
+
+ @Override
+ public boolean offerToKeepInParentForAnimation() {
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION)) {
+ mView.setKeepInParentForDismissAnimation(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeFromParentIfKeptForAnimation() {
+ ExpandableNotificationRow parent = mView.getNotificationParent();
+ if (mView.keepInParentForDismissAnimation() && parent != null) {
+ parent.removeChildNotification(mView);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void resetKeepInParentForAnimation() {
+ mView.setKeepInParentForDismissAnimation(false);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
new file mode 100644
index 0000000..ce11be3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.statusbar.notification.row
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class NotificationRowLogger @Inject constructor(@NotificationLog private val buffer: LogBuffer) {
+ fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = child.logKey
+ str2 = oldParent.logKey
+ },
+ { "Detach child $str1 kept in parent $str2" }
+ )
+ }
+
+ fun logSkipAttachingKeepInParentChild(child: NotificationEntry, newParent: NotificationEntry?) {
+ buffer.log(
+ TAG,
+ LogLevel.WARNING,
+ {
+ str1 = child.logKey
+ str2 = newParent.logKey
+ },
+ { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }
+ )
+ }
+}
+
+private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index b2628e4..6f4d6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -719,7 +719,7 @@
*/
public boolean isBouncerInTransit() {
return mStatusBarKeyguardViewManager != null
- && mStatusBarKeyguardViewManager.isBouncerInTransit();
+ && mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 0554fb5..d43ca823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -25,6 +25,7 @@
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.drawable.ColorDrawable;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
@@ -219,6 +220,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationChildrenContainer#onMeasure");
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
@@ -267,6 +269,7 @@
}
setMeasuredDimension(width, height);
+ Trace.endSection();
}
@Override
@@ -495,6 +498,20 @@
}
/**
+ * Sets the alpha on the content, while leaving the background of the container itself as is.
+ *
+ * @param alpha alpha value to apply to the content
+ */
+ public void setContentAlpha(float alpha) {
+ for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+ mNotificationHeader.getChildAt(i).setAlpha(alpha);
+ }
+ for (ExpandableNotificationRow child : getAttachedChildren()) {
+ child.setContentAlpha(alpha);
+ }
+ }
+
+ /**
* To be called any time the rows have been updated
*/
public void updateExpansionStates() {
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 2c3330e..2c096f5 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
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.os.Trace.TRACE_TAG_ALWAYS;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
@@ -30,6 +32,7 @@
import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,6 +47,7 @@
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Trace;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
@@ -188,6 +192,7 @@
/** Used to track the Y positions that were already used to draw debug text labels. */
private Set<Integer> mDebugTextUsedYPositions;
private final boolean mDebugRemoveAnimation;
+ private final boolean mSimplifiedAppearFraction;
private int mContentHeight;
private float mIntrinsicContentHeight;
@@ -568,6 +573,7 @@
FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
+ mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -1074,6 +1080,12 @@
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationStackScrollLayout#onMeasure");
+ if (SPEW) {
+ Log.d(TAG, "onMeasure("
+ + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
@@ -1090,6 +1102,13 @@
for (int i = 0; i < size; i++) {
measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
}
+ Trace.endSection();
+ }
+
+ @Override
+ public void requestLayout() {
+ Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout");
+ super.requestLayout();
}
@Override
@@ -1395,10 +1414,8 @@
}
int stackHeight;
float translationY;
- float appearEndPosition = getAppearEndPosition();
- float appearStartPosition = getAppearStartPosition();
float appearFraction = 1.0f;
- boolean appearing = height < appearEndPosition;
+ boolean appearing = calculateAppearFraction(height) < 1;
mAmbientState.setAppearing(appearing);
if (!appearing) {
translationY = 0;
@@ -1429,11 +1446,14 @@
} else {
// This may happen when pushing up a heads up. We linearly push it up from the
// start
- translationY = height - appearStartPosition + getExpandTranslationStart();
+ translationY = height - getAppearStartPosition() + getExpandTranslationStart();
}
stackHeight = (int) (height - translationY);
- if (isHeadsUpTransition()) {
- translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
+ if (isHeadsUpTransition() && appearFraction >= 0) {
+ int topSpacing = mShouldUseSplitNotificationShade
+ ? mAmbientState.getStackTopMargin() : mTopPadding;
+ float startPos = mHeadsUpInset - topSpacing;
+ translationY = MathUtils.lerp(startPos, 0, appearFraction);
}
}
mAmbientState.setAppearFraction(appearFraction);
@@ -1565,7 +1585,7 @@
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private float getAppearEndPosition() {
- int appearPosition = 0;
+ int appearPosition = mAmbientState.getStackTopMargin();
int visibleNotifCount = mController.getVisibleNotificationCount();
if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
if (isHeadsUpTransition()
@@ -1589,18 +1609,49 @@
return mAmbientState.getTrackedHeadsUpRow() != null;
}
- /**
- * @param height the height of the panel
- * @return the fraction of the appear animation that has been performed
- */
+ // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- public float calculateAppearFraction(float height) {
+ public float calculateAppearFractionOld(float height) {
float appearEndPosition = getAppearEndPosition();
float appearStartPosition = getAppearStartPosition();
return (height - appearStartPosition)
/ (appearEndPosition - appearStartPosition);
}
+ /**
+ * @param height the height of the panel
+ * @return Fraction of the appear animation that has been performed. Normally follows expansion
+ * fraction so goes from 0 to 1, the only exception is HUN where it can go negative, down to -1,
+ * when HUN is swiped up.
+ */
+ @FloatRange(from = -1.0, to = 1.0)
+ public float simplifiedAppearFraction(float height) {
+ if (isHeadsUpTransition()) {
+ // HUN is a special case because fraction can go negative if swiping up. And for now
+ // it must go negative as other pieces responsible for proper translation up assume
+ // negative value for HUN going up.
+ // This can't use expansion fraction as that goes only from 0 to 1. Also when
+ // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
+ // and that makes translation jump immediately. Let's use old implementation for now and
+ // see if we can figure out something better
+ return MathUtils.constrain(calculateAppearFractionOld(height), -1, 1);
+ } else {
+ return mAmbientState.getExpansionFraction();
+ }
+ }
+
+ public float calculateAppearFraction(float height) {
+ if (mSimplifiedAppearFraction) {
+ return simplifiedAppearFraction(height);
+ } else if (mShouldUseSplitNotificationShade) {
+ // for split shade we want to always use the new way of calculating appear fraction
+ // because without it heads-up experience is very broken and it's less risky change
+ return simplifiedAppearFraction(height);
+ } else {
+ return calculateAppearFractionOld(height);
+ }
+ }
+
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public float getStackTranslation() {
return mStackTranslation;
@@ -2723,6 +2774,10 @@
}
} else {
mSwipedOutViews.remove(child);
+
+ if (child instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) child).removeChildrenWithKeepInParent();
+ }
}
updateAnimationState(false, child);
@@ -3717,7 +3772,7 @@
}
}
- private void debugLog(@CompileTimeConstant String s) {
+ private void debugLog(@CompileTimeConstant final String s) {
if (mLogger == null) {
return;
}
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 e1337826..ad4501a 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
@@ -89,6 +89,7 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
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.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
@@ -157,6 +158,7 @@
private final NotifCollection mNotifCollection;
private final UiEventLogger mUiEventLogger;
private final NotificationRemoteInputManager mRemoteInputManager;
+ private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -441,7 +443,11 @@
if (!row.isDismissed()) {
handleChildViewDismissed(view);
}
+
row.removeFromTransientContainer();
+ if (row instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) row).removeChildrenWithKeepInParent();
+ }
}
/**
@@ -638,6 +644,7 @@
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -679,6 +686,7 @@
mNotifCollection = notifCollection;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
+ mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mShadeController = shadeController;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
@@ -750,6 +758,8 @@
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
+
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eea1d911..d8c6878 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -57,11 +57,13 @@
private float mGapHeight;
private float mGapHeightOnLockscreen;
private int mCollapsedSize;
+ private boolean mEnableNotificationClipping;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
private boolean mClipNotificationScrollToTop;
- @VisibleForTesting float mHeadsUpInset;
+ @VisibleForTesting
+ float mHeadsUpInset;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
@@ -85,6 +87,7 @@
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
@@ -289,7 +292,7 @@
// The bottom of this view is peeking out from under the previous view.
// Clip the part that is peeking out.
float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
- state.clipBottomAmount = (int) overlapAmount;
+ state.clipBottomAmount = mEnableNotificationClipping ? (int) overlapAmount : 0;
} else {
state.clipBottomAmount = 0;
}
@@ -454,7 +457,7 @@
/**
* @return Fraction to apply to view height and gap between views.
- * Does not include shelf height even if shelf is showing.
+ * Does not include shelf height even if shelf is showing.
*/
protected float getExpansionFractionWithoutShelf(
StackScrollAlgorithmState algorithmState,
@@ -468,7 +471,7 @@
&& (!ambientState.isBypassEnabled() || !ambientState.isPulseExpanding())
? 0 : mNotificationScrimPadding;
- final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
+ final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
if (stackEndHeight == 0f) {
// This should not happen, since even when the shade is empty we show EmptyShadeView
@@ -502,13 +505,14 @@
}
// TODO(b/172289889) polish shade open from HUN
+
/**
* Populates the {@link ExpandableViewState} for a single child.
*
- * @param i The index of the child in
- * {@link StackScrollAlgorithmState#visibleChildren}.
- * @param algorithmState The overall output state of the algorithm.
- * @param ambientState The input state provided to the algorithm.
+ * @param i The index of the child in
+ * {@link StackScrollAlgorithmState#visibleChildren}.
+ * @param algorithmState The overall output state of the algorithm.
+ * @param ambientState The input state provided to the algorithm.
*/
protected void updateChild(
int i,
@@ -582,8 +586,8 @@
final float stackBottom = !ambientState.isShadeExpanded()
|| ambientState.getDozeAmount() == 1f
|| bypassPulseNotExpanding
- ? ambientState.getInnerHeight()
- : ambientState.getStackHeight();
+ ? ambientState.getInnerHeight()
+ : ambientState.getStackHeight();
final float shelfStart = stackBottom
- ambientState.getShelf().getIntrinsicHeight()
- mPaddingBetweenElements;
@@ -619,9 +623,9 @@
* Get the gap height needed for before a view
*
* @param sectionProvider the sectionProvider used to understand the sections
- * @param visibleIndex the visible index of this view in the list
- * @param child the child asked about
- * @param previousChild the child right before it or null if none
+ * @param visibleIndex the visible index of this view in the list
+ * @param child the child asked about
+ * @param previousChild the child right before it or null if none
* @return the size of the gap needed or 0 if none is needed
*/
public float getGapHeightForChild(
@@ -655,9 +659,9 @@
* Does a given child need a gap, i.e spacing before a view?
*
* @param sectionProvider the sectionProvider used to understand the sections
- * @param visibleIndex the visible index of this view in the list
- * @param child the child asked about
- * @param previousChild the child right before it or null if none
+ * @param visibleIndex the visible index of this view in the list
+ * @param child the child asked about
+ * @param previousChild the child right before it or null if none
* @return if the child needs a gap height
*/
private boolean childNeedsGapHeight(
@@ -860,30 +864,53 @@
}
}
+ /**
+ * Calculate and update the Z positions for a given child. We currently only give shadows to
+ * HUNs to distinguish a HUN from its surroundings.
+ *
+ * @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the
+ * vertically top of screen. Top HUNs should have drop shadows
+ * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
+ * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
+ * that overlaps with QQS Panel. The integer part represents the count of
+ * previous HUNs whose Z positions are greater than 0.
+ */
protected float updateChildZValue(int i, float childrenOnTop,
StackScrollAlgorithmState algorithmState,
AmbientState ambientState,
- boolean shouldElevateHun) {
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
- int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
float baseZ = ambientState.getBaseZHeight();
+
+ // Handles HUN shadow when Shade is opened
+
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
&& childViewState.getYTranslation() < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
+ // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+ // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+ // When scrolling down shade to make HUN back to in-position in Notification Panel,
+ // The over-lapping fraction goes to 0, and shadows hides gradually.
if (childrenOnTop != 0.0f) {
+ // To elevate the later HUN over previous HUN
childrenOnTop++;
} else {
float overlap = ambientState.getTopPadding()
+ ambientState.getStackTranslation() - childViewState.getYTranslation();
- childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
+ // To prevent over-shadow during HUN entry
+ childrenOnTop += Math.min(
+ 1.0f,
+ overlap / childViewState.height
+ );
+ MathUtils.saturate(childrenOnTop);
}
childViewState.setZTranslation(baseZ
- + childrenOnTop * zDistanceBetweenElements);
- } else if (shouldElevateHun) {
+ + childrenOnTop * mPinnedZTranslationExtra);
+ } else if (isTopHun) {
// In case this is a new view that has never been measured before, we don't want to
- // elevate if we are currently expanded more then the notification
+ // elevate if we are currently expanded more than the notification
int shelfHeight = ambientState.getShelf() == null ? 0 :
ambientState.getShelf().getIntrinsicHeight();
float shelfStart = ambientState.getInnerHeight()
@@ -892,23 +919,28 @@
float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
+ mPaddingBetweenElements;
if (shelfStart > notificationEnd) {
+ // When the notification doesn't overlap with Notification Shelf, there's no shadow
childViewState.setZTranslation(baseZ);
} else {
+ // Give shadow to the notification if it overlaps with Notification Shelf
float factor = (notificationEnd - shelfStart) / shelfHeight;
if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
factor = 1.0f;
}
factor = Math.min(factor, 1.0f);
- childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements);
+ childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
}
} else {
childViewState.setZTranslation(baseZ);
}
- // We need to scrim the notification more from its surrounding content when we are pinned,
- // and we therefore elevate it higher.
- // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
- // expanding after which we have a normal elevation again.
+ // Handles HUN shadow when shade is closed.
+ // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
+ // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
+ // gradually from 0 to 1, shadow hides gradually.
+ // Header visibility is a deprecated concept, we are using headerVisibleAmount only because
+ // this value nicely goes from 0 to 1 during the HUN-to-Shade process.
+
childViewState.setZTranslation(childViewState.getZTranslation()
+ (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
return childrenOnTop;
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 2f7d344..cd3f3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -462,7 +462,7 @@
break;
case MODE_SHOW_BOUNCER:
Trace.beginSection("MODE_SHOW_BOUNCER");
- mKeyguardViewController.showBouncer(true);
+ mKeyguardViewController.showPrimaryBouncer(true);
Trace.endSection();
break;
case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
@@ -542,7 +542,7 @@
return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
}
if (mKeyguardStateController.isShowing()) {
- if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
+ if (mKeyguardViewController.primaryBouncerIsOrWillBeShowing() && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed) {
return MODE_UNLOCK_COLLAPSING;
@@ -585,7 +585,7 @@
return MODE_UNLOCK_COLLAPSING;
}
if (mKeyguardStateController.isShowing()) {
- if ((mKeyguardViewController.bouncerIsOrWillBeShowing()
+ if ((mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
|| mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed && (bypass || mAuthController.isUdfpsFingerDown())) {
@@ -745,6 +745,15 @@
}
@Override
+ public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) {
+ // When the bouncer is dismissed, treat this as a reset of the unlock mode. The user
+ // may have gone back instead of successfully unlocking
+ if (!bouncerIsOrWillBeShowing) {
+ resetMode();
+ }
+ }
+
+ @Override
public void dump(PrintWriter pw, String[] args) {
pw.println(" BiometricUnlockController:");
pw.print(" mMode="); pw.println(mMode);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 1961e1d..be08183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -54,7 +54,6 @@
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import java.io.PrintWriter;
@@ -254,12 +253,8 @@
boolean isWakeUpComingFromTouch();
- boolean isFalsingThresholdNeeded();
-
void onKeyguardViewManagerStatesUpdated();
- void setPanelExpanded(boolean isExpanded);
-
ViewGroup getNotificationScrollLayout();
boolean isPulsing();
@@ -415,12 +410,6 @@
void onClosingFinished();
- void onUnlockHintStarted();
-
- void onHintFinished();
-
- void onTrackingStopped(boolean expand);
-
// TODO: Figure out way to remove these.
NavigationBarView getNavigationBarView();
@@ -456,7 +445,11 @@
void setTransitionToFullShadeProgress(float transitionToFullShadeProgress);
- void setBouncerHiddenFraction(float expansion);
+ /**
+ * Sets the amount of progress to the bouncer being fully hidden/visible. 1 means the bouncer
+ * is fully hidden, while 0 means the bouncer is visible.
+ */
+ void setPrimaryBouncerHiddenFraction(float expansion);
@VisibleForTesting
void updateScrimController();
@@ -498,8 +491,6 @@
boolean isKeyguardSecure();
- NotificationGutsManager getGutsManager();
-
void updateNotificationPanelTouchState();
void makeExpandedVisible(boolean force);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 41f0520..f3482f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -55,6 +55,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
@@ -71,6 +72,8 @@
import javax.inject.Inject;
+import dagger.Lazy;
+
/** */
@CentralSurfacesComponent.CentralSurfacesScope
public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
@@ -99,6 +102,7 @@
private final boolean mVibrateOnOpening;
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
private final SystemBarAttributesListener mSystemBarAttributesListener;
+ private final Lazy<CameraLauncher> mCameraLauncherLazy;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -128,8 +132,8 @@
Optional<Vibrator> vibratorOptional,
DisableFlagsLogger disableFlagsLogger,
@DisplayId int displayId,
- SystemBarAttributesListener systemBarAttributesListener) {
-
+ SystemBarAttributesListener systemBarAttributesListener,
+ Lazy<CameraLauncher> cameraLauncherLazy) {
mCentralSurfaces = centralSurfaces;
mContext = context;
mShadeController = shadeController;
@@ -152,6 +156,7 @@
mVibratorOptional = vibratorOptional;
mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
+ mCameraLauncherLazy = cameraLauncherLazy;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -346,7 +351,8 @@
mCentralSurfaces.setLaunchCameraOnFinishedGoingToSleep(true);
return;
}
- if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
+ if (!mCameraLauncherLazy.get().canCameraGestureBeLaunched(
+ mNotificationPanelViewController.getBarState())) {
if (CentralSurfaces.DEBUG_CAMERA_LIFT) {
Slog.d(CentralSurfaces.TAG, "Can't launch camera right now");
}
@@ -383,7 +389,8 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
- mNotificationPanelViewController.launchCamera(source);
+ mCameraLauncherLazy.get().launchCamera(source,
+ mNotificationPanelViewController.isFullyCollapsed());
mCentralSurfaces.updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
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 6f952f5..eefceb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -166,22 +166,22 @@
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
@@ -231,6 +231,7 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -278,6 +279,7 @@
// 1020-1040 reserved for BaseStatusBar
/**
+ * TODO(b/249277686) delete this
* The delay to reset the hint text when the hint animation is finished running.
*/
private static final int HINT_RESET_DELAY_MS = 1200;
@@ -468,6 +470,7 @@
private final PluginManager mPluginManager;
private final ShadeController mShadeController;
private final InitController mInitController;
+ private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final PluginDependencyProvider mPluginDependencyProvider;
private final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -600,6 +603,7 @@
private Runnable mLaunchTransitionEndRunnable;
private Runnable mLaunchTransitionCancelRunnable;
+ private boolean mLaunchingAffordance;
private boolean mLaunchCameraWhenFinishedWaking;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private boolean mLaunchEmergencyActionWhenFinishedWaking;
@@ -744,7 +748,8 @@
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
- IDreamManager dreamManager) {
+ IDreamManager dreamManager,
+ Lazy<CameraLauncher> cameraLauncherLazy) {
mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -821,6 +826,7 @@
mMessageRouter = messageRouter;
mWallpaperManager = wallpaperManager;
mJankMonitor = jankMonitor;
+ mCameraLauncherLazy = cameraLauncherLazy;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -831,6 +837,7 @@
mScreenOffAnimationController = screenOffAnimationController;
mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
+ mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
mBubbleExpandListener = (isExpanding, key) ->
mContext.getMainExecutor().execute(this::updateScrimController);
@@ -1125,7 +1132,6 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
- mUserSwitcherController.init(mNotificationShadeWindowView);
// Allow plugins to reference DarkIconDispatcher and StatusBarStateController
mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
@@ -1359,6 +1365,7 @@
private void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
float fraction = event.getFraction();
boolean tracking = event.getTracking();
+ boolean isExpanded = event.getExpanded();
dispatchPanelExpansionForKeyguardDismiss(fraction, tracking);
if (fraction == 0 || fraction == 1) {
@@ -1371,6 +1378,23 @@
}
}
+ @VisibleForTesting
+ void onShadeExpansionFullyChanged(Boolean isExpanded) {
+ if (mPanelExpanded != isExpanded) {
+ mPanelExpanded = isExpanded;
+ if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
+ if (DEBUG) {
+ Log.v(TAG, "clearing notification effects from Height");
+ }
+ clearNotificationEffects();
+ }
+
+ if (!isExpanded) {
+ mRemoteInputManager.onPanelCollapsed();
+ }
+ }
+ }
+
@NonNull
@Override
public Lifecycle getLifecycle() {
@@ -1761,11 +1785,6 @@
return mWakeUpComingFromTouch;
}
- @Override
- public boolean isFalsingThresholdNeeded() {
- return true;
- }
-
/**
* To be called when there's a state change in StatusBarKeyguardViewManager.
*/
@@ -1775,27 +1794,6 @@
}
@Override
- public void setPanelExpanded(boolean isExpanded) {
- if (mPanelExpanded != isExpanded) {
- mNotificationLogger.onPanelExpandedChanged(isExpanded);
- }
- mPanelExpanded = isExpanded;
- mStatusBarHideIconsForBouncerManager.setPanelExpandedAndTriggerUpdate(isExpanded);
- mNotificationShadeWindowController.setPanelExpanded(isExpanded);
- mStatusBarStateController.setPanelExpanded(isExpanded);
- if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
- if (DEBUG) {
- Log.v(TAG, "clearing notification effects from Height");
- }
- clearNotificationEffects();
- }
-
- if (!isExpanded) {
- mRemoteInputManager.onPanelCollapsed();
- }
- }
-
- @Override
public ViewGroup getNotificationScrollLayout() {
return mStackScroller;
}
@@ -2247,13 +2245,6 @@
}
pw.println(" Panels: ");
- if (mNotificationPanelViewController != null) {
- pw.println(" mNotificationPanel="
- + mNotificationPanelViewController.getView() + " params="
- + mNotificationPanelViewController.getView().getLayoutParams().debug(""));
- pw.print (" ");
- mNotificationPanelViewController.dump(pw, args);
- }
pw.println(" mStackScroller: " + mStackScroller + " (dump moved)");
pw.println(" Theme:");
String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + "";
@@ -2958,7 +2949,7 @@
private void onLaunchTransitionFadingEnded() {
mNotificationPanelViewController.resetAlpha();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mKeyguardStateController.setLaunchTransitionFadingAway(false);
@@ -3028,7 +3019,7 @@
private void onLaunchTransitionTimeout() {
Log.w(TAG, "Launch transition: Timeout!");
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
mNotificationPanelViewController.resetViews(false /* animate */);
}
@@ -3081,7 +3072,7 @@
}
mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
releaseGestureWakeLock();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
mNotificationPanelViewController.resetAlpha();
mNotificationPanelViewController.resetTranslation();
mNotificationPanelViewController.resetViewGroupFade();
@@ -3239,7 +3230,7 @@
@Override
public void endAffordanceLaunch() {
releaseGestureWakeLock();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
}
/**
@@ -3302,9 +3293,9 @@
// lock screen where users can use the UDFPS affordance to enter the device
mStatusBarKeyguardViewManager.reset(true);
} else if (mState == StatusBarState.KEYGUARD
- && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()
+ && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
&& isKeyguardSecure()) {
- mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
+ mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
}
}
}
@@ -3397,22 +3388,6 @@
}
}
- @Override
- public void onUnlockHintStarted() {
- mFalsingCollector.onUnlockHintStarted();
- mKeyguardIndicationController.showActionToUnlock();
- }
-
- @Override
- public void onHintFinished() {
- // Delay the reset a bit so the user can read the text.
- mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
- }
-
- @Override
- public void onTrackingStopped(boolean expand) {
- }
-
// TODO: Figure out way to remove these.
@Override
public NavigationBarView getNavigationBarView() {
@@ -3512,7 +3487,7 @@
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
mLaunchCameraWhenFinishedWaking = false;
mDeviceInteractive = false;
@@ -3613,7 +3588,8 @@
.updateSensitivenessForOccludedWakeup();
}
if (mLaunchCameraWhenFinishedWaking) {
- mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource);
+ mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
+ mNotificationPanelViewController.isFullyCollapsed());
mLaunchCameraWhenFinishedWaking = false;
}
if (mLaunchEmergencyActionWhenFinishedWaking) {
@@ -3786,7 +3762,7 @@
* is fully hidden, while 0 means the bouncer is visible.
*/
@Override
- public void setBouncerHiddenFraction(float expansion) {
+ public void setPrimaryBouncerHiddenFraction(float expansion) {
mScrimController.setBouncerHiddenFraction(expansion);
}
@@ -3804,11 +3780,10 @@
mScrimController.setExpansionAffectsAlpha(!unlocking);
- boolean launchingAffordanceWithPreview =
- mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
+ boolean launchingAffordanceWithPreview = mLaunchingAffordance;
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
|| mTransitionToFullShadeProgress > 0f) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -3819,7 +3794,7 @@
// Bouncer needs the front scrim when it's on top of an activity,
// tapping on a notification, editing QS or being dismissed by
// FLAG_DISMISS_KEYGUARD_ACTIVITY.
- ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
+ ScrimState state = mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming()
? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
mScrimController.transitionTo(state);
} else if (launchingAffordanceWithPreview) {
@@ -4128,7 +4103,7 @@
*/
@Override
public boolean isBouncerShowingScrimmed() {
- return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming();
+ return isBouncerShowing() && mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming();
}
@Override
@@ -4143,11 +4118,6 @@
// End Extra BaseStatusBarMethods.
- @Override
- public NotificationGutsManager getGutsManager() {
- return mGutsManager;
- }
-
boolean isTransientShown() {
return mTransientShown;
}
@@ -4270,7 +4240,6 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- mUserSwitcherController.onDensityOrFontScaleChanged();
mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
mHeadsUpManager.onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 34cd1ce..7dcdc0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -33,7 +33,7 @@
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
- private var maxBounds: Rect? = null
+ private var maxBounds = Rect()
private var fontScale: Float = 0.toFloat()
private val inCarMode: Boolean
private var uiMode: Int = 0
@@ -47,6 +47,7 @@
fontScale = currentConfig.fontScale
density = currentConfig.densityDpi
smallestScreenWidth = currentConfig.smallestScreenWidthDp
+ maxBounds.set(currentConfig.windowConfiguration.maxBounds)
inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
Configuration.UI_MODE_TYPE_CAR
uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -92,7 +93,11 @@
val maxBounds = newConfig.windowConfiguration.maxBounds
if (maxBounds != this.maxBounds) {
- this.maxBounds = maxBounds
+ // Update our internal rect to have the same bounds, instead of using
+ // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds`
+ // would be a direct reference to windowConfiguration.maxBounds, so the if statement
+ // above would always fail. See b/245799099 for more information.
+ this.maxBounds.set(maxBounds)
listeners.filterForEach({ this.listeners.contains(it) }) {
it.onMaxBoundsChanged()
}
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 103e4f6..3743fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -34,6 +34,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -111,7 +112,8 @@
ConfigurationController configurationController,
@Main Handler handler,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ ShadeExpansionStateManager shadeExpansionStateManager) {
super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
@@ -132,6 +134,8 @@
updateResources();
}
});
+
+ shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -221,13 +225,7 @@
mTrackingHeadsUp = trackingHeadsUp;
}
- /**
- * Notify that the status bar panel gets expanded or collapsed.
- *
- * @param isExpanded True to notify expanded, false to notify collapsed.
- * TODO(b/237811427) replace with a listener
- */
- public void setIsPanelExpanded(boolean isExpanded) {
+ private void onShadeExpansionFullyChanged(Boolean isExpanded) {
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 4897c52..78b28d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -23,6 +23,8 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.widget.FrameLayout
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.LockIconViewController
import com.android.systemui.R
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
@@ -51,13 +53,20 @@
private var ambientIndicationArea: View? = null
private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
+ private lateinit var lockIconViewController: LockIconViewController
/** Initializes the view. */
fun init(
viewModel: KeyguardBottomAreaViewModel,
falsingManager: FalsingManager,
+ lockIconViewController: LockIconViewController,
) {
- binding = bind(this, viewModel, falsingManager)
+ binding = bind(
+ this,
+ viewModel,
+ falsingManager,
+ )
+ this.lockIconViewController = lockIconViewController
}
/**
@@ -114,4 +123,29 @@
}
return insets
}
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ findViewById<View>(R.id.ambient_indication_container)?.let {
+ val (ambientLeft, ambientTop) = it.locationOnScreen
+ if (binding.shouldConstrainToTopOfLockIcon()) {
+ //make top of ambient indication view the bottom of the lock icon
+ it.layout(
+ ambientLeft,
+ lockIconViewController.bottom.toInt(),
+ right - ambientLeft,
+ ambientTop + it.measuredHeight
+ )
+ } else {
+ //make bottom of ambient indication view the top of the lock icon
+ val lockLocationTop = lockIconViewController.top
+ it.layout(
+ ambientLeft,
+ lockLocationTop.toInt() - it.measuredHeight,
+ right - ambientLeft,
+ lockLocationTop.toInt()
+ )
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index a28fca1..aa0757e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -55,13 +55,13 @@
import javax.inject.Inject;
/**
- * A class which manages the bouncer on the lockscreen.
+ * A class which manages the primary (pin/pattern/password) bouncer on the lockscreen.
* @deprecated Use KeyguardBouncerRepository
*/
@Deprecated
public class KeyguardBouncer {
- private static final String TAG = "KeyguardBouncer";
+ private static final String TAG = "PrimaryKeyguardBouncer";
static final long BOUNCER_FACE_DELAY = 1200;
public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
/**
@@ -78,7 +78,7 @@
private final FalsingCollector mFalsingCollector;
private final DismissCallbackRegistry mDismissCallbackRegistry;
private final Handler mHandler;
- private final List<BouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
+ private final List<PrimaryBouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardSecurityModel mKeyguardSecurityModel;
@@ -126,7 +126,7 @@
private KeyguardBouncer(Context context, ViewMediatorCallback callback,
ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
- BouncerExpansionCallback expansionCallback,
+ PrimaryBouncerExpansionCallback expansionCallback,
KeyguardStateController keyguardStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardBypassController keyguardBypassController, @Main Handler handler,
@@ -279,10 +279,7 @@
* @see #onFullyShown()
*/
private void onFullyHidden() {
- cancelShowRunnable();
- setVisibility(View.INVISIBLE);
- mFalsingCollector.onBouncerHidden();
- DejankUtils.postAfterTraversal(mResetRunnable);
+
}
private void setVisibility(@View.Visibility int visibility) {
@@ -459,7 +456,13 @@
onFullyShown();
dispatchFullyShown();
} else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
- onFullyHidden();
+ DejankUtils.postAfterTraversal(mResetRunnable);
+ /*
+ * There are cases where #hide() was not invoked, such as when
+ * NotificationPanelViewController controls the hide animation. Make sure the state gets
+ * updated by calling #hide() directly.
+ */
+ hide(false /* destroyView */);
dispatchFullyHidden();
} else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
dispatchStartingToHide();
@@ -571,37 +574,37 @@
}
private void dispatchFullyShown() {
- for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
callback.onFullyShown();
}
}
private void dispatchStartingToHide() {
- for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
callback.onStartingToHide();
}
}
private void dispatchStartingToShow() {
- for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
callback.onStartingToShow();
}
}
private void dispatchFullyHidden() {
- for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
callback.onFullyHidden();
}
}
private void dispatchExpansionChanged() {
- for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
callback.onExpansionChanged(mExpansion);
}
}
private void dispatchVisibilityChanged() {
- for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+ for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
callback.onVisibilityChanged(mContainer.getVisibility() == View.VISIBLE);
}
}
@@ -647,7 +650,7 @@
/**
* Adds a callback to listen to bouncer expansion updates.
*/
- public void addBouncerExpansionCallback(BouncerExpansionCallback callback) {
+ public void addBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
if (!mExpansionCallbacks.contains(callback)) {
mExpansionCallbacks.add(callback);
}
@@ -657,11 +660,14 @@
* Removes a previously added callback. If the callback was never added, this methood
* does nothing.
*/
- public void removeBouncerExpansionCallback(BouncerExpansionCallback callback) {
+ public void removeBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
mExpansionCallbacks.remove(callback);
}
- public interface BouncerExpansionCallback {
+ /**
+ * Callback updated when the primary bouncer's show and hide states change.
+ */
+ public interface PrimaryBouncerExpansionCallback {
/**
* Invoked when the bouncer expansion reaches {@link KeyguardBouncer#EXPANSION_VISIBLE}.
* This is NOT called each time the bouncer is shown, but rather only when the fully
@@ -745,7 +751,7 @@
* Construct a KeyguardBouncer that will exist in the given container.
*/
public KeyguardBouncer create(ViewGroup container,
- BouncerExpansionCallback expansionCallback) {
+ PrimaryBouncerExpansionCallback expansionCallback) {
return new KeyguardBouncer(mContext, mCallback, container,
mDismissCallbackRegistry, mFalsingCollector, expansionCallback,
mKeyguardStateController, mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 18877f9..13566ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -26,6 +26,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue;
@@ -47,6 +48,9 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -69,7 +73,7 @@
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
- private ViewGroup mUserSwitcherContainer;
+ private StatusBarUserSwitcherContainer mUserSwitcherContainer;
private boolean mKeyguardUserSwitcherEnabled;
private boolean mKeyguardUserAvatarEnabled;
@@ -120,8 +124,12 @@
loadDimens();
}
- public ViewGroup getUserSwitcherContainer() {
- return mUserSwitcherContainer;
+ /**
+ * Should only be called from {@link KeyguardStatusBarViewController}
+ * @param viewModel view model for the status bar user chip
+ */
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel);
}
@Override
@@ -303,10 +311,7 @@
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.removeRule(RelativeLayout.RIGHT_OF);
lp.width = LayoutParams.WRAP_CONTENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(getResources().getDimensionPixelSize(
+ lp.setMarginStart(getResources().getDimensionPixelSize(
R.dimen.system_icons_super_container_margin_start));
return true;
}
@@ -338,10 +343,7 @@
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view);
lp.width = LayoutParams.MATCH_PARENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(0);
+ lp.setMarginStart(0);
return true;
}
@@ -527,4 +529,11 @@
mClipRect.set(0, mTopClipping, getWidth(), getHeight());
setClipBounds(mClipRect);
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("KeyguardStatusBarView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 14cebf4..d4dc1dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -59,13 +59,11 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
@@ -110,9 +108,7 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
private final UserManager mUserManager;
- private final StatusBarUserSwitcherFeatureController mFeatureController;
- private final StatusBarUserSwitcherController mUserSwitcherController;
- private final StatusBarUserInfoTracker mStatusBarUserInfoTracker;
+ private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
private final SecureSettings mSecureSettings;
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
@@ -276,9 +272,7 @@
SysuiStatusBarStateController statusBarStateController,
StatusBarContentInsetsProvider statusBarContentInsetsProvider,
UserManager userManager,
- StatusBarUserSwitcherFeatureController featureController,
- StatusBarUserSwitcherController userSwitcherController,
- StatusBarUserInfoTracker statusBarUserInfoTracker,
+ StatusBarUserChipViewModel userChipViewModel,
SecureSettings secureSettings,
CommandQueue commandQueue,
@Main Executor mainExecutor,
@@ -301,9 +295,7 @@
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
mUserManager = userManager;
- mFeatureController = featureController;
- mUserSwitcherController = userSwitcherController;
- mStatusBarUserInfoTracker = statusBarUserInfoTracker;
+ mStatusBarUserChipViewModel = userChipViewModel;
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
@@ -328,8 +320,7 @@
R.dimen.header_notifications_collide_distance);
mView.setKeyguardUserAvatarEnabled(
- !mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
- mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
+ !mStatusBarUserChipViewModel.getChipEnabled());
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
mDisableStateTracker = new DisableStateTracker(
@@ -344,11 +335,11 @@
super.onInit();
mCarrierTextController.init();
mBatteryMeterViewController.init();
- mUserSwitcherController.init();
}
@Override
protected void onViewAttached() {
+ mView.init(mStatusBarUserChipViewModel);
mConfigurationController.addCallback(mConfigurationListener);
mAnimationScheduler.addCallback(mAnimationCallback);
mUserInfoController.addCallback(mOnUserInfoChangedListener);
@@ -394,9 +385,6 @@
/** Sets whether user switcher is enabled. */
public void setKeyguardUserSwitcherEnabled(boolean enabled) {
mView.setKeyguardUserSwitcherEnabled(enabled);
- // We don't have a listener for when the user switcher setting changes, so this is
- // where we re-check the state
- mStatusBarUserInfoTracker.checkEnabled();
}
/** Sets whether this controller should listen to battery updates. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 16fddb42..6bf5443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
@@ -41,12 +42,54 @@
/**
* Class to control all aspects about light bar changes.
*/
-public class LightBarTransitionsController implements Dumpable, Callbacks,
- StatusBarStateController.StateListener {
+public class LightBarTransitionsController implements Dumpable {
public static final int DEFAULT_TINT_ANIMATION_DURATION = 120;
private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
+ private static class Callback implements Callbacks, StatusBarStateController.StateListener {
+ private final WeakReference<LightBarTransitionsController> mSelf;
+
+ Callback(LightBarTransitionsController self) {
+ mSelf = new WeakReference<>(self);
+ }
+
+ @Override
+ public void appTransitionPending(int displayId, boolean forced) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.appTransitionPending(displayId, forced);
+ }
+ }
+
+ @Override
+ public void appTransitionCancelled(int displayId) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.appTransitionCancelled(displayId);
+ }
+ }
+
+ @Override
+ public void appTransitionStarting(int displayId, long startTime, long duration,
+ boolean forced) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.appTransitionStarting(displayId, startTime, duration, forced);
+ }
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.onDozeAmountChanged(linear, eased);
+ }
+ }
+ }
+
+ private final Callback mCallback;
+
private final Handler mHandler;
private final DarkIntensityApplier mApplier;
private final KeyguardStateController mKeyguardStateController;
@@ -86,8 +129,9 @@
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
mCommandQueue = commandQueue;
- mCommandQueue.addCallback(this);
- mStatusBarStateController.addCallback(this);
+ mCallback = new Callback(this);
+ mCommandQueue.addCallback(mCallback);
+ mStatusBarStateController.addCallback(mCallback);
mDozeAmount = mStatusBarStateController.getDozeAmount();
mContext = context;
mDisplayId = mContext.getDisplayId();
@@ -95,8 +139,8 @@
/** Call to cleanup the LightBarTransitionsController when done with it. */
public void destroy() {
- mCommandQueue.removeCallback(this);
- mStatusBarStateController.removeCallback(this);
+ mCommandQueue.removeCallback(mCallback);
+ mStatusBarStateController.removeCallback(mCallback);
}
public void saveState(Bundle outState) {
@@ -110,16 +154,14 @@
mNextDarkIntensity = mDarkIntensity;
}
- @Override
- public void appTransitionPending(int displayId, boolean forced) {
+ private void appTransitionPending(int displayId, boolean forced) {
if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
return;
}
mTransitionPending = true;
}
- @Override
- public void appTransitionCancelled(int displayId) {
+ private void appTransitionCancelled(int displayId) {
if (mDisplayId != displayId) {
return;
}
@@ -131,9 +173,7 @@
mTransitionPending = false;
}
- @Override
- public void appTransitionStarting(int displayId, long startTime, long duration,
- boolean forced) {
+ private void appTransitionStarting(int displayId, long startTime, long duration, boolean forced) {
if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
return;
}
@@ -230,10 +270,6 @@
pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
}
- @Override
- public void onStateChanged(int newState) { }
-
- @Override
public void onDozeAmountChanged(float linear, float eased) {
mDozeAmount = eased;
dispatchDark();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 8793a57..1d7dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.WallpaperColors;
@@ -45,6 +44,7 @@
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 libcore.io.IoUtils;
@@ -82,10 +82,11 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
DumpManager dumpManager,
NotificationMediaManager mediaManager,
- @Main Handler mainHandler) {
+ @Main Handler mainHandler,
+ UserTracker userTracker) {
dumpManager.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
- mCurrentUserId = ActivityManager.getCurrentUser();
+ mCurrentUserId = userTracker.getUserId();
mUpdateMonitor = keyguardUpdateMonitor;
mMediaManager = mediaManager;
mH = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 94d1bf4..26e6db6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -14,7 +14,6 @@
package com.android.systemui.statusbar.phone;
-import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -28,6 +27,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -44,6 +44,7 @@
private final Context mContext;
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
private final LinkedList<UserInfo> mProfiles;
private boolean mListening;
@@ -52,9 +53,11 @@
/**
*/
@Inject
- public ManagedProfileControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public ManagedProfileControllerImpl(Context context, UserTracker userTracker,
+ BroadcastDispatcher broadcastDispatcher) {
mContext = context;
mUserManager = UserManager.get(mContext);
+ mUserTracker = userTracker;
mBroadcastDispatcher = broadcastDispatcher;
mProfiles = new LinkedList<UserInfo>();
}
@@ -90,7 +93,7 @@
private void reloadManagedProfiles() {
synchronized (mProfiles) {
boolean hadProfile = mProfiles.size() > 0;
- int user = ActivityManager.getCurrentUser();
+ int user = mUserTracker.getUserId();
mProfiles.clear();
for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index 3811689..4fe03017 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -25,7 +25,7 @@
import com.android.systemui.plugins.NotificationListenerController;
import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7aeb08d..28bc64d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,6 +38,9 @@
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.leak.RotationUtils;
import java.util.Objects;
@@ -73,6 +76,11 @@
mTouchEventHandler = handler;
}
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
+ StatusBarUserChipViewBinder.bind(container, viewModel);
+ }
+
@Override
public void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f9c4c8f..a6c2b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,11 +23,11 @@
import android.view.ViewTreeObserver
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UNFOLD_STATUS_BAR
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
@@ -40,7 +40,7 @@
view: PhoneStatusBarView,
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
touchEventHandler: PhoneStatusBarView.TouchEventHandler,
private val configurationController: ConfigurationController
@@ -91,10 +91,10 @@
init {
mView.setTouchEventHandler(touchEventHandler)
+ mView.init(userChipViewModel)
}
override fun onInit() {
- userSwitcherController.init()
}
fun setImportantForAccessibility(mode: Int) {
@@ -156,9 +156,9 @@
private val unfoldComponent: Optional<SysUIUnfoldComponent>,
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- private val configurationController: ConfigurationController
+ private val configurationController: ConfigurationController,
) {
fun create(
view: PhoneStatusBarView,
@@ -168,7 +168,7 @@
view,
progressProvider.getOrNull(),
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
touchEventHandler,
configurationController
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 e744c79..fb0d3e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,7 +53,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -111,6 +110,12 @@
private boolean mClipsQsScrim;
/**
+ * Whether an activity is launching over the lockscreen. During the launch animation, we want to
+ * delay certain scrim changes until after the animation ends.
+ */
+ private boolean mOccludeAnimationPlaying = false;
+
+ /**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade.
@@ -205,7 +210,6 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private KeyguardViewMediator mKeyguardViewMediator;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -275,8 +279,7 @@
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- KeyguardViewMediator keyguardViewMediator) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -315,8 +318,6 @@
}
});
mColors = new GradientColors();
-
- mKeyguardViewMediator = keyguardViewMediator;
}
/**
@@ -738,6 +739,11 @@
return mClipsQsScrim;
}
+ public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
+ mOccludeAnimationPlaying = occludeAnimationPlaying;
+ applyAndDispatchState();
+ }
+
private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
if (scrim == null) {
return;
@@ -777,11 +783,15 @@
}
if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
- // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
- // because we're doing the screen off animation OR the shade is collapsing because
- // we're playing the unlock animation
+ final boolean occluding =
+ mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
+
+ // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
+ // screen off/occlusion animations, ignore expansion changes while those animations
+ // play.
if (!mScreenOffAnimationController.shouldExpandNotifications()
- && !mAnimatingPanelExpansionOnUnlock) {
+ && !mAnimatingPanelExpansionOnUnlock
+ && !occluding) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
if (mClipsQsScrim) {
@@ -812,13 +822,6 @@
mBehindTint,
interpolatedFraction);
}
-
- // If we're unlocked but still playing the occlude animation, remain at the keyguard
- // alpha temporarily.
- if (mKeyguardViewMediator.isOccludeAnimationPlaying()
- || mState.mLaunchingAffordanceWithPreview) {
- mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
- }
} else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
@@ -895,7 +898,7 @@
float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha();
float behindAlpha;
- int behindTint;
+ int behindTint = state.getBehindTint();
if (mDarkenWhileDragging) {
behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
interpolatedFract);
@@ -903,17 +906,19 @@
behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
interpolatedFract);
}
- if (mClipsQsScrim) {
- behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
+ if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
+ if (mClipsQsScrim) {
+ behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
state.getNotifTint(), interpolatedFract);
- } else {
- behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+ } else {
+ behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
state.getBehindTint(), interpolatedFract);
+ }
}
if (mQsExpansion > 0) {
behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
float tintProgress = mQsExpansion;
- if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
+ if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
// this is case of - on lockscreen - going from expanded QS to bouncer.
// Because mQsExpansion is already interpolated and transition between tints
// is too slow, we want to speed it up and make it more aligned to bouncer
@@ -1096,7 +1101,7 @@
}
private float getInterpolatedFraction() {
- if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
+ if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
return BouncerPanelExpansionCalculator
.aboutToShowBouncerProgress(mPanelExpansionFraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 5512bed..3d6bebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -72,9 +72,9 @@
//resize the layout. Let's
// make sure that the window stays small for one frame until the
//touchableRegion is set.
- mNotificationPanelViewController.getView().requestLayout();
+ mNotificationPanelViewController.requestLayoutOnView();
mNotificationShadeWindowController.setForceWindowCollapsed(true);
- mNotificationPanelViewController.getView().post(() -> {
+ mNotificationPanelViewController.postToView(() -> {
mNotificationShadeWindowController.setForceWindowCollapsed(false);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
index 5113191..4d9de09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
@@ -5,6 +5,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -24,10 +25,11 @@
*/
@SysUISingleton
class StatusBarHideIconsForBouncerManager @Inject constructor(
- private val commandQueue: CommandQueue,
- @Main private val mainExecutor: DelayableExecutor,
- statusBarWindowStateController: StatusBarWindowStateController,
- dumpManager: DumpManager
+ private val commandQueue: CommandQueue,
+ @Main private val mainExecutor: DelayableExecutor,
+ statusBarWindowStateController: StatusBarWindowStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
+ dumpManager: DumpManager
) : Dumpable {
// State variables set by external classes.
private var panelExpanded: Boolean = false
@@ -47,6 +49,12 @@
statusBarWindowStateController.addListener {
state -> setStatusBarStateAndTriggerUpdate(state)
}
+ shadeExpansionStateManager.addFullExpansionListener { isExpanded ->
+ if (panelExpanded != isExpanded) {
+ panelExpanded = isExpanded
+ updateHideIconsForBouncer(animate = false)
+ }
+ }
}
/** Returns true if the status bar icons should be hidden in the bouncer. */
@@ -63,11 +71,6 @@
this.displayId = displayId
}
- fun setPanelExpandedAndTriggerUpdate(panelExpanded: Boolean) {
- this.panelExpanded = panelExpanded
- updateHideIconsForBouncer(animate = false)
- }
-
fun setIsOccludedAndTriggerUpdate(isOccluded: Boolean) {
this.isOccluded = isOccluded
updateHideIconsForBouncer(animate = false)
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 21b8762..d464210 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -20,6 +20,7 @@
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
@@ -60,8 +61,8 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -77,8 +78,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
@@ -135,62 +135,64 @@
@Nullable
private final FoldAodAnimationController mFoldAodAnimationController;
private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
- private final BouncerCallbackInteractor mBouncerCallbackInteractor;
- private final BouncerInteractor mBouncerInteractor;
- private final BouncerView mBouncerView;
+ private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+ private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ private final BouncerView mPrimaryBouncerView;
private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
- private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
- private boolean mBouncerAnimating;
+ private final PrimaryBouncerExpansionCallback mExpansionCallback =
+ new PrimaryBouncerExpansionCallback() {
+ private boolean mPrimaryBouncerAnimating;
- @Override
- public void onFullyShown() {
- mBouncerAnimating = false;
- updateStates();
- }
-
- @Override
- public void onStartingToHide() {
- mBouncerAnimating = true;
- updateStates();
- }
-
- @Override
- public void onStartingToShow() {
- mBouncerAnimating = true;
- updateStates();
- }
-
- @Override
- public void onFullyHidden() {
- mBouncerAnimating = false;
- }
-
- @Override
- public void onExpansionChanged(float expansion) {
- if (mBouncerAnimating) {
- mCentralSurfaces.setBouncerHiddenFraction(expansion);
- }
- }
-
- @Override
- public void onVisibilityChanged(boolean isVisible) {
- mCentralSurfaces
- .setBouncerShowingOverDream(
- isVisible && mDreamOverlayStateController.isOverlayActive());
-
- if (!isVisible) {
- mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
+ @Override
+ public void onFullyShown() {
+ mPrimaryBouncerAnimating = false;
+ updateStates();
}
- /* Register predictive back callback when keyguard becomes visible, and unregister
- when it's hidden. */
- if (isVisible) {
- registerBackCallback();
- } else {
- unregisterBackCallback();
+ @Override
+ public void onStartingToHide() {
+ mPrimaryBouncerAnimating = true;
+ updateStates();
}
- }
+
+ @Override
+ public void onStartingToShow() {
+ mPrimaryBouncerAnimating = true;
+ updateStates();
+ }
+
+ @Override
+ public void onFullyHidden() {
+ mPrimaryBouncerAnimating = false;
+ updateStates();
+ }
+
+ @Override
+ public void onExpansionChanged(float expansion) {
+ if (mPrimaryBouncerAnimating) {
+ mCentralSurfaces.setPrimaryBouncerHiddenFraction(expansion);
+ }
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean isVisible) {
+ mCentralSurfaces.setBouncerShowingOverDream(
+ isVisible && mDreamOverlayStateController.isOverlayActive());
+
+ if (!isVisible) {
+ mCentralSurfaces.setPrimaryBouncerHiddenFraction(
+ KeyguardBouncer.EXPANSION_HIDDEN);
+ }
+
+ /* Register predictive back callback when keyguard becomes visible, and unregister
+ when it's hidden. */
+ if (isVisible) {
+ registerBackCallback();
+ } else {
+ unregisterBackCallback();
+ }
+ }
};
private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
@@ -216,14 +218,14 @@
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
- protected CentralSurfaces mCentralSurfaces;
+ @Nullable protected CentralSurfaces mCentralSurfaces;
private NotificationPanelViewController mNotificationPanelViewController;
private BiometricUnlockController mBiometricUnlockController;
private boolean mCentralSurfacesRegistered;
private View mNotificationContainer;
- @Nullable protected KeyguardBouncer mBouncer;
+ @Nullable protected KeyguardBouncer mPrimaryBouncer;
protected boolean mRemoteInputActive;
private boolean mGlobalActionsVisible = false;
private boolean mLastGlobalActionsVisible = false;
@@ -236,8 +238,8 @@
protected boolean mFirstUpdate = true;
protected boolean mLastShowing;
protected boolean mLastOccluded;
- private boolean mLastBouncerShowing;
- private boolean mLastBouncerIsOrWillBeShowing;
+ private boolean mLastPrimaryBouncerShowing;
+ private boolean mLastPrimaryBouncerIsOrWillBeShowing;
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
private boolean mLastDozing;
@@ -264,8 +266,8 @@
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
private final LatencyTracker mLatencyTracker;
private final KeyguardSecurityModel mKeyguardSecurityModel;
- private KeyguardBypassController mBypassController;
- @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
+ @Nullable private KeyguardBypassController mBypassController;
+ @Nullable private AlternateBouncer mAlternateBouncer;
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@@ -300,9 +302,9 @@
LatencyTracker latencyTracker,
KeyguardSecurityModel keyguardSecurityModel,
FeatureFlags featureFlags,
- BouncerCallbackInteractor bouncerCallbackInteractor,
- BouncerInteractor bouncerInteractor,
- BouncerView bouncerView) {
+ PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
+ PrimaryBouncerInteractor primaryBouncerInteractor,
+ BouncerView primaryBouncerView) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -320,9 +322,9 @@
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
- mBouncerCallbackInteractor = bouncerCallbackInteractor;
- mBouncerInteractor = bouncerInteractor;
- mBouncerView = bouncerView;
+ mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
+ mPrimaryBouncerInteractor = primaryBouncerInteractor;
+ mPrimaryBouncerView = primaryBouncerView;
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
@@ -340,9 +342,9 @@
ViewGroup container = mCentralSurfaces.getBouncerContainer();
if (mIsModernBouncerEnabled) {
- mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
+ mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
} else {
- mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
+ mPrimaryBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
}
mNotificationPanelViewController = notificationPanelViewController;
if (shadeExpansionStateManager != null) {
@@ -361,20 +363,20 @@
* Sets the given alt auth interceptor to null if it's the current auth interceptor. Else,
* does nothing.
*/
- public void removeAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
- if (Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
- mAlternateAuthInterceptor = null;
- resetAlternateAuth(true);
+ public void removeAlternateAuthInterceptor(@NonNull AlternateBouncer authInterceptor) {
+ if (Objects.equals(mAlternateBouncer, authInterceptor)) {
+ mAlternateBouncer = null;
+ hideAlternateBouncer(true);
}
}
/**
* Sets a new alt auth interceptor.
*/
- public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
- if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
- mAlternateAuthInterceptor = authInterceptor;
- resetAlternateAuth(false);
+ public void setAlternateBouncer(@NonNull AlternateBouncer authInterceptor) {
+ if (!Objects.equals(mAlternateBouncer, authInterceptor)) {
+ mAlternateBouncer = authInterceptor;
+ hideAlternateBouncer(false);
}
}
@@ -458,48 +460,49 @@
if (mDozing && !mPulsing) {
return;
} else if (mNotificationPanelViewController.isUnlockHintRunning()) {
- if (mBouncer != null) {
- mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
return;
- } else if (bouncerNeedsScrimming()) {
- if (mBouncer != null) {
- mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ } else if (needsFullscreenBouncer()) {
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else {
- mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
} else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
+ && !(mBiometricUnlockController.getMode() == MODE_SHOW_BOUNCER)
&& !isUnlockCollapsing()) {
- if (mBouncer != null) {
- mBouncer.setExpansion(fraction);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.setExpansion(fraction);
} else {
- mBouncerInteractor.setPanelExpansion(fraction);
+ mPrimaryBouncerInteractor.setPanelExpansion(fraction);
}
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
&& !mKeyguardStateController.canDismissLockScreen()
- && !bouncerIsShowing()
+ && !primaryBouncerIsShowing()
&& !bouncerIsAnimatingAway()) {
- if (mBouncer != null) {
- mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
} else {
- mBouncerInteractor.show(/* isScrimmed= */false);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
}
}
- } else if (!mKeyguardStateController.isShowing() && isBouncerInTransit()) {
+ } else if (!mKeyguardStateController.isShowing() && isPrimaryBouncerInTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
// We need to hide the bouncer, otherwise it will be stuck in transit.
- if (mBouncer != null) {
- mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -543,17 +546,17 @@
if (needsFullscreenBouncer() && !mDozing) {
// The keyguard might be showing (already). So we need to hide it.
mCentralSurfaces.hideKeyguard();
- if (mBouncer != null) {
- mBouncer.show(true /* resetSecuritySelection */);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.show(true /* resetSecuritySelection */);
} else {
- mBouncerInteractor.show(true);
+ mPrimaryBouncerInteractor.show(true);
}
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
hideBouncer(false /* destroyView */);
- if (mBouncer != null) {
- mBouncer.prepare();
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.prepare();
}
}
}
@@ -561,23 +564,25 @@
}
/**
- * If applicable, shows the alternate authentication bouncer. Else, shows the input
- * (pin/password/pattern) bouncer.
- * @param scrimmed true when the input bouncer should show scrimmed, false when the user will be
- * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
+ *
+ * If possible, shows the alternate bouncer. Else, shows the primary (pin/pattern/password)
+ * bouncer.
+ * @param scrimmed true when the primary bouncer should show scrimmed,
+ * false when the user will be dragging it and translation should be deferred
+ * {@see KeyguardBouncer#show(boolean, boolean)}
*/
- public void showGenericBouncer(boolean scrimmed) {
- if (shouldShowAltAuth()) {
- updateAlternateAuthShowing(mAlternateAuthInterceptor.showAlternateAuthBouncer());
+ public void showBouncer(boolean scrimmed) {
+ if (canShowAlternateBouncer()) {
+ updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
return;
}
- showBouncer(scrimmed);
+ showPrimaryBouncer(scrimmed);
}
- /** Whether we should show the alternate authentication instead of the traditional bouncer. */
- public boolean shouldShowAltAuth() {
- return mAlternateAuthInterceptor != null
+ /** Whether we can show the alternate bouncer instead of the primary bouncer. */
+ public boolean canShowAlternateBouncer() {
+ return mAlternateBouncer != null
&& mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);
}
@@ -586,10 +591,10 @@
*/
@VisibleForTesting
void hideBouncer(boolean destroyView) {
- if (mBouncer != null) {
- mBouncer.hide(destroyView);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.hide(destroyView);
} else {
- mBouncerInteractor.hide();
+ mPrimaryBouncerInteractor.hide();
}
if (mKeyguardStateController.isShowing()) {
// If we were showing the bouncer and then aborting, we need to also clear out any
@@ -600,19 +605,19 @@
}
/**
- * Shows the keyguard input bouncer - the password challenge on the lock screen
+ * Shows the primary bouncer - the pin/pattern/password challenge on the lock screen.
*
* @param scrimmed true when the bouncer should show scrimmed, false when the user will be
* dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
*/
- public void showBouncer(boolean scrimmed) {
- resetAlternateAuth(false);
+ public void showPrimaryBouncer(boolean scrimmed) {
+ hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
- if (mBouncer != null) {
- mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.show(false /* resetSecuritySelection */, scrimmed);
} else {
- mBouncerInteractor.show(scrimmed);
+ mPrimaryBouncerInteractor.show(scrimmed);
}
}
updateStates();
@@ -644,42 +649,41 @@
// If there is an an alternate auth interceptor (like the UDFPS), show that one
// instead of the bouncer.
- if (shouldShowAltAuth()) {
+ if (canShowAlternateBouncer()) {
if (!afterKeyguardGone) {
- if (mBouncer != null) {
- mBouncer.setDismissAction(mAfterKeyguardGoneAction,
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
} else {
- mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+ mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
}
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
}
- updateAlternateAuthShowing(
- mAlternateAuthInterceptor.showAlternateAuthBouncer());
+ updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
return;
}
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
- if (mBouncer != null) {
- mBouncer.show(false /* resetSecuritySelection */);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.show(false /* resetSecuritySelection */);
} else {
- mBouncerInteractor.show(/* isScrimmed= */true);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
}
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
- if (mBouncer != null) {
- mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
} else {
- mBouncerInteractor.setDismissAction(
+ mPrimaryBouncerInteractor.setDismissAction(
mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
- mBouncerInteractor.show(/* isScrimmed= */true);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
}
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
@@ -724,28 +728,34 @@
} else {
showBouncerOrKeyguard(hideBouncerWhenShowing);
}
- resetAlternateAuth(false);
+ hideAlternateBouncer(false);
mKeyguardUpdateManager.sendKeyguardReset();
updateStates();
}
}
@Override
- public void resetAlternateAuth(boolean forceUpdateScrim) {
- final boolean updateScrim = (mAlternateAuthInterceptor != null
- && mAlternateAuthInterceptor.hideAlternateAuthBouncer())
+ public void hideAlternateBouncer(boolean forceUpdateScrim) {
+ final boolean updateScrim = (mAlternateBouncer != null
+ && mAlternateBouncer.hideAlternateBouncer())
|| forceUpdateScrim;
- updateAlternateAuthShowing(updateScrim);
+ updateAlternateBouncerShowing(updateScrim);
}
- private void updateAlternateAuthShowing(boolean updateScrim) {
- final boolean isShowingAltAuth = isShowingAlternateAuth();
+ private void updateAlternateBouncerShowing(boolean updateScrim) {
+ if (!mCentralSurfacesRegistered) {
+ // if CentralSurfaces hasn't been registered yet, then the controllers below haven't
+ // been initialized yet so there's no need to attempt to forward them events.
+ return;
+ }
+
+ final boolean isShowingAlternateBouncer = isShowingAlternateBouncer();
if (mKeyguardMessageAreaController != null) {
- mKeyguardMessageAreaController.setIsVisible(isShowingAltAuth);
+ mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
mKeyguardMessageAreaController.setMessage("");
}
- mBypassController.setAltBouncerShowing(isShowingAltAuth);
- mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAltAuth);
+ mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
+ mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAlternateBouncer);
if (updateScrim) {
mCentralSurfaces.updateScrimController();
@@ -782,10 +792,10 @@
@Override
public void onFinishedGoingToSleep() {
- if (mBouncer != null) {
- mBouncer.onScreenTurnedOff();
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.onScreenTurnedOff();
} else {
- mBouncerInteractor.onScreenTurnedOff();
+ mPrimaryBouncerInteractor.onScreenTurnedOff();
}
}
@@ -870,18 +880,18 @@
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
}
- if (animate && !isOccluded && isShowing && !bouncerIsShowing()) {
+ if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
mCentralSurfaces.animateKeyguardUnoccluding();
}
}
@Override
public void startPreHideAnimation(Runnable finishRunnable) {
- if (bouncerIsShowing()) {
- if (mBouncer != null) {
- mBouncer.startPreHideAnimation(finishRunnable);
+ if (primaryBouncerIsShowing()) {
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.startPreHideAnimation(finishRunnable);
} else {
- mBouncerInteractor.startDisappearAnimation(finishRunnable);
+ mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable);
}
mNotificationPanelViewController.startBouncerPreHideAnimation();
@@ -994,19 +1004,19 @@
updateResources();
return;
}
- boolean wasShowing = bouncerIsShowing();
- boolean wasScrimmed = bouncerIsScrimmed();
+ boolean wasShowing = primaryBouncerIsShowing();
+ boolean wasScrimmed = primaryBouncerIsScrimmed();
hideBouncer(true /* destroyView */);
- mBouncer.prepare();
+ mPrimaryBouncer.prepare();
- if (wasShowing) showBouncer(wasScrimmed);
+ if (wasShowing) showPrimaryBouncer(wasScrimmed);
}
public void onKeyguardFadedAway() {
mNotificationContainer.postDelayed(() -> mNotificationShadeWindowController
.setKeyguardFadingAway(false), 100);
- ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
+ mNotificationPanelViewController.resetViewAlphas();
mCentralSurfaces.finishKeyguardFadingAway();
mBiometricUnlockController.finishKeyguardFadingAway();
WindowManagerGlobal.getInstance().trimMemory(
@@ -1044,8 +1054,8 @@
* WARNING: This method might cause Binder calls.
*/
public boolean isSecure() {
- if (mBouncer != null) {
- return mBouncer.isSecure();
+ if (mPrimaryBouncer != null) {
+ return mPrimaryBouncer.isSecure();
}
return mKeyguardSecurityModel.getSecurityMode(
@@ -1059,7 +1069,7 @@
* @return whether a back press can be handled right now.
*/
public boolean canHandleBackPressed() {
- return bouncerIsShowing();
+ return primaryBouncerIsShowing();
}
/**
@@ -1072,7 +1082,7 @@
mCentralSurfaces.endAffordanceLaunch();
// The second condition is for SIM card locked bouncer
- if (bouncerIsScrimmed() && !needsFullscreenBouncer()) {
+ if (primaryBouncerIsScrimmed() && !needsFullscreenBouncer()) {
hideBouncer(false);
updateStates();
} else {
@@ -1093,27 +1103,27 @@
@Override
public boolean isBouncerShowing() {
- return bouncerIsShowing() || isShowingAlternateAuth();
+ return primaryBouncerIsShowing() || isShowingAlternateBouncer();
}
@Override
- public boolean bouncerIsOrWillBeShowing() {
- return isBouncerShowing() || isBouncerInTransit();
+ public boolean primaryBouncerIsOrWillBeShowing() {
+ return isBouncerShowing() || isPrimaryBouncerInTransit();
}
public boolean isFullscreenBouncer() {
- if (mBouncerView.getDelegate() != null) {
- return mBouncerView.getDelegate().isFullScreenBouncer();
+ if (mPrimaryBouncerView.getDelegate() != null) {
+ return mPrimaryBouncerView.getDelegate().isFullScreenBouncer();
}
- return mBouncer != null && mBouncer.isFullscreenBouncer();
+ return mPrimaryBouncer != null && mPrimaryBouncer.isFullscreenBouncer();
}
/**
* Clear out any potential actions that were saved to run when the device is unlocked
*/
public void cancelPostAuthActions() {
- if (bouncerIsOrWillBeShowing()) {
- return; // allow bouncer to trigger saved actions
+ if (primaryBouncerIsOrWillBeShowing()) {
+ return; // allow the primary bouncer to trigger saved actions
}
mAfterKeyguardGoneAction = null;
mDismissActionWillAnimateOnKeyguard = false;
@@ -1152,25 +1162,25 @@
}
boolean showing = mKeyguardStateController.isShowing();
boolean occluded = mKeyguardStateController.isOccluded();
- boolean bouncerShowing = bouncerIsShowing();
- boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
- boolean bouncerDismissible = !isFullscreenBouncer();
+ boolean primaryBouncerShowing = primaryBouncerIsShowing();
+ boolean primaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing();
+ boolean primaryBouncerDismissible = !isFullscreenBouncer();
boolean remoteInputActive = mRemoteInputActive;
- if ((bouncerDismissible || !showing || remoteInputActive) !=
- (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
+ if ((primaryBouncerDismissible || !showing || remoteInputActive)
+ != (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
|| mFirstUpdate) {
- if (bouncerDismissible || !showing || remoteInputActive) {
- if (mBouncer != null) {
- mBouncer.setBackButtonEnabled(true);
+ if (primaryBouncerDismissible || !showing || remoteInputActive) {
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.setBackButtonEnabled(true);
} else {
- mBouncerInteractor.setBackButtonEnabled(true);
+ mPrimaryBouncerInteractor.setBackButtonEnabled(true);
}
} else {
- if (mBouncer != null) {
- mBouncer.setBackButtonEnabled(false);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.setBackButtonEnabled(false);
} else {
- mBouncerInteractor.setBackButtonEnabled(false);
+ mPrimaryBouncerInteractor.setBackButtonEnabled(false);
}
}
}
@@ -1181,23 +1191,26 @@
updateNavigationBarVisibility(navBarVisible);
}
- if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
- mNotificationShadeWindowController.setBouncerShowing(bouncerShowing);
- mCentralSurfaces.setBouncerShowing(bouncerShowing);
+ boolean isPrimaryBouncerShowingChanged =
+ primaryBouncerShowing != mLastPrimaryBouncerShowing;
+ mLastPrimaryBouncerShowing = primaryBouncerShowing;
+
+ if (isPrimaryBouncerShowingChanged || mFirstUpdate) {
+ mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
+ mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
}
- if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
- || bouncerShowing != mLastBouncerShowing) {
- mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing,
- bouncerShowing);
+ if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
+ || isPrimaryBouncerShowingChanged) {
+ mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+ primaryBouncerShowing);
}
mFirstUpdate = false;
mLastShowing = showing;
mLastGlobalActionsVisible = mGlobalActionsVisible;
mLastOccluded = occluded;
- mLastBouncerShowing = bouncerShowing;
- mLastBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing;
- mLastBouncerDismissible = bouncerDismissible;
+ mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing;
+ mLastBouncerDismissible = primaryBouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
mLastPulsing = mPulsing;
@@ -1241,7 +1254,7 @@
|| mPulsing && !mIsDocked)
&& mGesturalNav;
return (!keyguardVisible && !hideWhileDozing && !mScreenOffAnimationPlaying
- || bouncerIsShowing()
+ || primaryBouncerIsShowing()
|| mRemoteInputActive
|| keyguardWithGestureNav
|| mGlobalActionsVisible);
@@ -1257,32 +1270,32 @@
&& !mLastScreenOffAnimationPlaying || mLastPulsing && !mLastIsDocked)
&& mLastGesturalNav;
return (!keyguardShowing && !hideWhileDozing && !mLastScreenOffAnimationPlaying
- || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
+ || mLastPrimaryBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
|| mLastGlobalActionsVisible);
}
public boolean shouldDismissOnMenuPressed() {
- if (mBouncerView.getDelegate() != null) {
- return mBouncerView.getDelegate().shouldDismissOnMenuPressed();
+ if (mPrimaryBouncerView.getDelegate() != null) {
+ return mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed();
}
- return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
+ return mPrimaryBouncer != null && mPrimaryBouncer.shouldDismissOnMenuPressed();
}
public boolean interceptMediaKey(KeyEvent event) {
- if (mBouncerView.getDelegate() != null) {
- return mBouncerView.getDelegate().interceptMediaKey(event);
+ if (mPrimaryBouncerView.getDelegate() != null) {
+ return mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
}
- return mBouncer != null && mBouncer.interceptMediaKey(event);
+ return mPrimaryBouncer != null && mPrimaryBouncer.interceptMediaKey(event);
}
/**
* @return true if the pre IME back event should be handled
*/
public boolean dispatchBackKeyEventPreIme() {
- if (mBouncerView.getDelegate() != null) {
- return mBouncerView.getDelegate().dispatchBackKeyEventPreIme();
+ if (mPrimaryBouncerView.getDelegate() != null) {
+ return mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
}
- return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
+ return mPrimaryBouncer != null && mPrimaryBouncer.dispatchBackKeyEventPreIme();
}
public void readyForKeyguardDone() {
@@ -1328,29 +1341,29 @@
* fingerprint.
*/
public void notifyKeyguardAuthenticated(boolean strongAuth) {
- if (mBouncer != null) {
- mBouncer.notifyKeyguardAuthenticated(strongAuth);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.notifyKeyguardAuthenticated(strongAuth);
} else {
- mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
+ mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
- resetAlternateAuth(false);
+ if (mAlternateBouncer != null && isShowingAlternateBouncer()) {
+ hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
}
/** Display security message to relevant KeyguardMessageArea. */
public void setKeyguardMessage(String message, ColorStateList colorState) {
- if (isShowingAlternateAuth()) {
+ if (isShowingAlternateBouncer()) {
if (mKeyguardMessageAreaController != null) {
mKeyguardMessageAreaController.setMessage(message);
}
} else {
- if (mBouncer != null) {
- mBouncer.showMessage(message, colorState);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.showMessage(message, colorState);
} else {
- mBouncerInteractor.showMessage(message, colorState);
+ mPrimaryBouncerInteractor.showMessage(message, colorState);
}
}
}
@@ -1389,12 +1402,15 @@
}
}
- public boolean bouncerNeedsScrimming() {
+ /**
+ * Whether the primary bouncer requires scrimming.
+ */
+ public boolean primaryBouncerNeedsScrimming() {
// When a dream overlay is active, scrimming will cause any expansion to immediately expand.
return (mKeyguardStateController.isOccluded()
&& !mDreamOverlayStateController.isOverlayActive())
- || bouncerWillDismissWithAction()
- || (bouncerIsShowing() && bouncerIsScrimmed())
+ || primaryBouncerWillDismissWithAction()
+ || (primaryBouncerIsShowing() && primaryBouncerIsScrimmed())
|| isFullscreenBouncer();
}
@@ -1404,10 +1420,10 @@
* configuration.
*/
public void updateResources() {
- if (mBouncer != null) {
- mBouncer.updateResources();
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.updateResources();
} else {
- mBouncerInteractor.updateResources();
+ mPrimaryBouncerInteractor.updateResources();
}
}
@@ -1419,19 +1435,20 @@
pw.println(" mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables);
pw.println(" mPendingWakeupAction: " + mPendingWakeupAction);
pw.println(" isBouncerShowing(): " + isBouncerShowing());
- pw.println(" bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+ pw.println(" bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
pw.println(" Registered KeyguardViewManagerCallbacks:");
for (KeyguardViewManagerCallback callback : mCallbacks) {
pw.println(" " + callback);
}
- if (mBouncer != null) {
- mBouncer.dump(pw);
+ if (mPrimaryBouncer != null) {
+ pw.println("PrimaryBouncer:");
+ mPrimaryBouncer.dump(pw);
}
- if (mAlternateAuthInterceptor != null) {
- pw.println("AltAuthInterceptor: ");
- mAlternateAuthInterceptor.dump(pw);
+ if (mAlternateBouncer != null) {
+ pw.println("AlternateBouncer:");
+ mAlternateBouncer.dump(pw);
}
}
@@ -1479,13 +1496,12 @@
}
@Nullable
- public KeyguardBouncer getBouncer() {
- return mBouncer;
+ public KeyguardBouncer getPrimaryBouncer() {
+ return mPrimaryBouncer;
}
- public boolean isShowingAlternateAuth() {
- return mAlternateAuthInterceptor != null
- && mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
+ public boolean isShowingAlternateBouncer() {
+ return mAlternateBouncer != null && mAlternateBouncer.isShowingAlternateBouncer();
}
/**
@@ -1499,10 +1515,10 @@
/** Update keyguard position based on a tapped X coordinate. */
public void updateKeyguardPosition(float x) {
- if (mBouncer != null) {
- mBouncer.updateKeyguardPosition(x);
+ if (mPrimaryBouncer != null) {
+ mPrimaryBouncer.updateKeyguardPosition(x);
} else {
- mBouncerInteractor.setKeyguardPosition(x);
+ mPrimaryBouncerInteractor.setKeyguardPosition(x);
}
}
@@ -1534,41 +1550,41 @@
*/
public void requestFp(boolean request, int udfpsColor) {
mKeyguardUpdateManager.requestFingerprintAuthOnOccludingApp(request);
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.requestUdfps(request, udfpsColor);
+ if (mAlternateBouncer != null) {
+ mAlternateBouncer.requestUdfps(request, udfpsColor);
}
}
/**
* Returns if bouncer expansion is between 0 and 1 non-inclusive.
*/
- public boolean isBouncerInTransit() {
- if (mBouncer != null) {
- return mBouncer.inTransit();
+ public boolean isPrimaryBouncerInTransit() {
+ if (mPrimaryBouncer != null) {
+ return mPrimaryBouncer.inTransit();
} else {
- return mBouncerInteractor.isInTransit();
+ return mPrimaryBouncerInteractor.isInTransit();
}
}
/**
* Returns if bouncer is showing
*/
- public boolean bouncerIsShowing() {
- if (mBouncer != null) {
- return mBouncer.isShowing();
+ public boolean primaryBouncerIsShowing() {
+ if (mPrimaryBouncer != null) {
+ return mPrimaryBouncer.isShowing();
} else {
- return mBouncerInteractor.isFullyShowing();
+ return mPrimaryBouncerInteractor.isFullyShowing();
}
}
/**
* Returns if bouncer is scrimmed
*/
- public boolean bouncerIsScrimmed() {
- if (mBouncer != null) {
- return mBouncer.isScrimmed();
+ public boolean primaryBouncerIsScrimmed() {
+ if (mPrimaryBouncer != null) {
+ return mPrimaryBouncer.isScrimmed();
} else {
- return mBouncerInteractor.isScrimmed();
+ return mPrimaryBouncerInteractor.isScrimmed();
}
}
@@ -1576,10 +1592,10 @@
* Returns if bouncer is animating away
*/
public boolean bouncerIsAnimatingAway() {
- if (mBouncer != null) {
- return mBouncer.isAnimatingAway();
+ if (mPrimaryBouncer != null) {
+ return mPrimaryBouncer.isAnimatingAway();
} else {
- return mBouncerInteractor.isAnimatingAway();
+ return mPrimaryBouncerInteractor.isAnimatingAway();
}
}
@@ -1587,11 +1603,11 @@
/**
* Returns if bouncer will dismiss with action
*/
- public boolean bouncerWillDismissWithAction() {
- if (mBouncer != null) {
- return mBouncer.willDismissWithAction();
+ public boolean primaryBouncerWillDismissWithAction() {
+ if (mPrimaryBouncer != null) {
+ return mPrimaryBouncer.willDismissWithAction();
} else {
- return mBouncerInteractor.willDismissWithAction();
+ return mPrimaryBouncerInteractor.willDismissWithAction();
}
}
@@ -1606,26 +1622,26 @@
}
/**
- * Delegate used to send show/reset events to an alternate authentication method instead of the
- * regular pin/pattern/password bouncer.
+ * Delegate used to send show and hide events to an alternate authentication method instead of
+ * the regular pin/pattern/password bouncer.
*/
- public interface AlternateAuthInterceptor {
+ public interface AlternateBouncer {
/**
* Show alternate authentication bouncer.
* @return whether alternate auth method was newly shown
*/
- boolean showAlternateAuthBouncer();
+ boolean showAlternateBouncer();
/**
* Hide alternate authentication bouncer
* @return whether the alternate auth method was newly hidden
*/
- boolean hideAlternateAuthBouncer();
+ boolean hideAlternateBouncer();
/**
* @return true if the alternate auth bouncer is showing
*/
- boolean isShowingAlternateAuthBouncer();
+ boolean isShowingAlternateBouncer();
/**
* Use when an app occluding the keyguard would like to give the user ability to
@@ -1637,7 +1653,7 @@
void requestUdfps(boolean requestUdfps, int color);
/**
- * print information for the alternate auth interceptor registered
+ * print information for the alternate bouncer registered
*/
void dump(PrintWriter pw);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 70af77e..8a49850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -133,7 +133,7 @@
if (!row.isPinned()) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
}
- mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
+ mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
mPendingRemoteInputView = clicked;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d9c0293..2a039da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -34,6 +34,7 @@
import com.android.systemui.R;
import com.android.systemui.ScreenDecorations;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -68,12 +69,15 @@
private int mDisplayCutoutTouchableRegionSize;
private int mStatusBarHeight;
+ private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
+
@Inject
public StatusBarTouchableRegionManager(
Context context,
NotificationShadeWindowController notificationShadeWindowController,
ConfigurationController configurationController,
HeadsUpManagerPhone headsUpManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController
) {
mContext = context;
@@ -101,17 +105,7 @@
updateTouchableRegion();
}
});
- mHeadsUpManager.addHeadsUpPhoneListener(
- new HeadsUpManagerPhone.OnHeadsUpPhoneListenerChange() {
- @Override
- public void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway) {
- if (!headsUpGoingAway) {
- updateTouchableRegionAfterLayout();
- } else {
- updateTouchableRegion();
- }
- }
- });
+ mHeadsUpManager.addHeadsUpPhoneListener(this::onHeadsUpGoingAwayStateChanged);
mNotificationShadeWindowController = notificationShadeWindowController;
mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
@@ -119,6 +113,9 @@
});
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+
+ mOnComputeInternalInsetsListener = this::onComputeInternalInsets;
}
protected void setup(
@@ -136,17 +133,11 @@
pw.println(mTouchableRegion);
}
- /**
- * Notify that the status bar panel gets expanded or collapsed.
- *
- * @param isExpanded True to notify expanded, false to notify collapsed.
- * TODO(b/237811427) replace with a listener
- */
- public void setPanelExpanded(boolean isExpanded) {
+ private void onShadeExpansionFullyChanged(Boolean isExpanded) {
if (isExpanded != mIsStatusBarExpanded) {
mIsStatusBarExpanded = isExpanded;
if (isExpanded) {
- // make sure our state is sane
+ // make sure our state is sensible
mForceCollapsedUntilLayout = false;
}
updateTouchableRegion();
@@ -260,18 +251,22 @@
|| mUnlockedScreenOffAnimationController.isAnimationPlaying();
}
- private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener =
- new OnComputeInternalInsetsListener() {
- @Override
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (shouldMakeEntireScreenTouchable()) {
- return;
- }
-
- // Update touch insets to include any area needed for touching features that live in
- // the status bar (ie: heads up notifications)
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(calculateTouchableRegion());
+ private void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway) {
+ if (!headsUpGoingAway) {
+ updateTouchableRegionAfterLayout();
+ } else {
+ updateTouchableRegion();
}
- };
+ }
+
+ private void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (shouldMakeEntireScreenTouchable()) {
+ return;
+ }
+
+ // Update touch insets to include any area needed for touching features that live in
+ // the status bar (ie: heads up notifications)
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(calculateTouchableRegion());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
index e7d9221..678c2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
@@ -87,7 +87,7 @@
private void updateDialogListeners() {
if (shouldHideAffordance()) {
- mKeyguardViewController.resetAlternateAuth(true);
+ mKeyguardViewController.hideAlternateBouncer(true);
}
for (Listener listener : mListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 0369845..344d233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -28,13 +28,13 @@
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.biometrics.AuthRippleView;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
import com.android.systemui.shade.NotificationPanelView;
@@ -220,20 +220,22 @@
@Named(LARGE_SCREEN_BATTERY_CONTROLLER)
static BatteryMeterViewController getBatteryMeterViewController(
@Named(SPLIT_SHADE_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+ UserTracker userTracker,
ConfigurationController configurationController,
TunerService tunerService,
- BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController
) {
return new BatteryMeterViewController(
batteryMeterView,
+ userTracker,
configurationController,
tunerService,
- broadcastDispatcher,
mainHandler,
contentResolver,
+ featureFlags,
batteryController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 41f1f95..efec270 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -29,8 +29,6 @@
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -39,7 +37,6 @@
import javax.inject.Named;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
@@ -126,12 +123,6 @@
}
/** */
- @Binds
- @StatusBarFragmentScope
- StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
-
- /** */
@Provides
@StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
deleted file mode 100644
index f6b8cb0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ /dev/null
@@ -1,134 +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.statusbar.phone.userswitcher
-
-import android.graphics.drawable.Drawable
-import android.os.UserManager
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Since every user switcher chip will user the exact same information and logic on whether or not
- * to show, and what data to show, it makes sense to create a single tracker here
- */
-@SysUISingleton
-class StatusBarUserInfoTracker @Inject constructor(
- private val userInfoController: UserInfoController,
- private val userManager: UserManager,
- private val dumpManager: DumpManager,
- @Main private val mainExecutor: Executor,
- @Background private val backgroundExecutor: Executor
-) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
- var currentUserName: String? = null
- private set
- var currentUserAvatar: Drawable? = null
- private set
- var userSwitcherEnabled = false
- private set
- private var listening = false
-
- private val listeners = mutableListOf<CurrentUserChipInfoUpdatedListener>()
-
- private val userInfoChangedListener = OnUserInfoChangedListener { name, picture, _ ->
- currentUserAvatar = picture
- currentUserName = name
- notifyListenersUserInfoChanged()
- }
-
- init {
- dumpManager.registerDumpable(TAG, this)
- }
-
- override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
- if (listeners.isEmpty()) {
- startListening()
- }
-
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: CurrentUserChipInfoUpdatedListener) {
- listeners.remove(listener)
-
- if (listeners.isEmpty()) {
- stopListening()
- }
- }
-
- private fun notifyListenersUserInfoChanged() {
- listeners.forEach {
- it.onCurrentUserChipInfoUpdated()
- }
- }
-
- private fun notifyListenersSettingChanged() {
- listeners.forEach {
- it.onStatusBarUserSwitcherSettingChanged(userSwitcherEnabled)
- }
- }
-
- private fun startListening() {
- listening = true
- userInfoController.addCallback(userInfoChangedListener)
- }
-
- private fun stopListening() {
- listening = false
- userInfoController.removeCallback(userInfoChangedListener)
- }
-
- /**
- * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
- * changed
- */
- fun checkEnabled() {
- backgroundExecutor.execute {
- // Check on a background thread to avoid main thread Binder calls
- val wasEnabled = userSwitcherEnabled
- userSwitcherEnabled = userManager.isUserSwitcherEnabled
-
- if (wasEnabled != userSwitcherEnabled) {
- mainExecutor.execute {
- notifyListenersSettingChanged()
- }
- }
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(" userSwitcherEnabled=$userSwitcherEnabled")
- pw.println(" listening=$listening")
- }
-}
-
-interface CurrentUserChipInfoUpdatedListener {
- fun onCurrentUserChipInfoUpdated()
- fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
-}
-
-private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
deleted file mode 100644
index e498ae4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ /dev/null
@@ -1,112 +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.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.view.View
-import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.UserSwitcherActivity
-import com.android.systemui.util.ViewController
-
-import javax.inject.Inject
-
-/**
- * ViewController for [StatusBarUserSwitcherContainer]
- */
-class StatusBarUserSwitcherControllerImpl @Inject constructor(
- view: StatusBarUserSwitcherContainer,
- private val tracker: StatusBarUserInfoTracker,
- private val featureController: StatusBarUserSwitcherFeatureController,
- private val userSwitcherDialogController: UserSwitchDialogController,
- private val featureFlags: FeatureFlags,
- private val activityStarter: ActivityStarter,
- private val falsingManager: FalsingManager
-) : ViewController<StatusBarUserSwitcherContainer>(view),
- StatusBarUserSwitcherController {
- private val listener = object : CurrentUserChipInfoUpdatedListener {
- override fun onCurrentUserChipInfoUpdated() {
- updateChip()
- }
-
- override fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- private val featureFlagListener = object : OnUserSwitcherPreferenceChangeListener {
- override fun onUserSwitcherPreferenceChange(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- public override fun onViewAttached() {
- tracker.addCallback(listener)
- featureController.addCallback(featureFlagListener)
- mView.setOnClickListener { view: View ->
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return@setOnClickListener
- }
-
- if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- val intent = Intent(context, UserSwitcherActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-
- activityStarter.startActivity(intent, true /* dismissShade */,
- null /* ActivityLaunchAnimator.Controller */,
- true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
- } else {
- userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
- }
- }
-
- updateEnabled()
- }
-
- override fun onViewDetached() {
- tracker.removeCallback(listener)
- featureController.removeCallback(featureFlagListener)
- mView.setOnClickListener(null)
- }
-
- private fun updateChip() {
- mView.text.text = tracker.currentUserName
- mView.avatar.setImageDrawable(tracker.currentUserAvatar)
- }
-
- private fun updateEnabled() {
- if (featureController.isStatusBarUserSwitcherFeatureEnabled() &&
- tracker.userSwitcherEnabled) {
- mView.visibility = View.VISIBLE
- updateChip()
- } else {
- mView.visibility = View.GONE
- }
- }
-}
-
-interface StatusBarUserSwitcherController {
- fun init()
-}
-
-private const val TAG = "SbUserSwitcherController"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
deleted file mode 100644
index 7bae9ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
+++ /dev/null
@@ -1,63 +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.statusbar.phone.userswitcher
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.policy.CallbackController
-
-import javax.inject.Inject
-
-@SysUISingleton
-class StatusBarUserSwitcherFeatureController @Inject constructor(
- private val flags: FeatureFlags
-) : CallbackController<OnUserSwitcherPreferenceChangeListener> {
- private val listeners = mutableListOf<OnUserSwitcherPreferenceChangeListener>()
-
- init {
- flags.addListener(Flags.STATUS_BAR_USER_SWITCHER) {
- it.requestNoRestart()
- notifyListeners()
- }
- }
-
- fun isStatusBarUserSwitcherFeatureEnabled(): Boolean {
- return flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- }
-
- override fun addCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- listeners.remove(listener)
- }
-
- private fun notifyListeners() {
- val enabled = flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- listeners.forEach {
- it.onUserSwitcherPreferenceChange(enabled)
- }
- }
-}
-
-interface OnUserSwitcherPreferenceChangeListener {
- fun onUserSwitcherPreferenceChange(enabled: Boolean)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
index 60bd038..501467f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
@@ -32,6 +32,7 @@
interface MobileMappingsProxy {
fun mapIconSets(config: Config): Map<String, MobileIconGroup>
fun getDefaultIcons(config: Config): MobileIconGroup
+ fun getIconKey(displayInfo: TelephonyDisplayInfo): String
fun toIconKey(@NetworkType networkType: Int): String
fun toIconKeyOverride(@NetworkType networkType: Int): String
}
@@ -44,6 +45,9 @@
override fun getDefaultIcons(config: Config): MobileIconGroup =
MobileMappings.getDefaultIcons(config)
+ override fun getIconKey(displayInfo: TelephonyDisplayInfo): String =
+ MobileMappings.getIconKey(displayInfo)
+
override fun toIconKey(@NetworkType networkType: Int): String =
MobileMappings.toIconKey(networkType)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index cf4106c..68d30d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -21,7 +21,6 @@
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.drawable.Drawable
-import android.os.UserHandle
import android.widget.BaseAdapter
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
@@ -84,7 +83,7 @@
}
fun refresh() {
- controller.refreshUsers(UserHandle.USER_NULL)
+ controller.refreshUsers()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 149ed0a..d10d7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -155,6 +155,9 @@
default void onWirelessChargingChanged(boolean isWirlessCharging) {
}
+
+ default void onIsOverheatedChanged(boolean isOverheated) {
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index c7ad767..2ee5232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy;
+import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_PRESENT;
import android.annotation.WorkerThread;
@@ -87,6 +90,7 @@
protected boolean mPowerSave;
private boolean mAodPowerSave;
private boolean mWirelessCharging;
+ private boolean mIsOverheated = false;
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
@@ -152,6 +156,7 @@
pw.print(" mPluggedIn="); pw.println(mPluggedIn);
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
+ pw.print(" mIsOverheated="); pw.println(mIsOverheated);
pw.print(" mPowerSave="); pw.println(mPowerSave);
pw.print(" mStateUnknown="); pw.println(mStateUnknown);
}
@@ -184,6 +189,7 @@
cb.onPowerSaveChanged(mPowerSave);
cb.onBatteryUnknownStateChanged(mStateUnknown);
cb.onWirelessChargingChanged(mWirelessCharging);
+ cb.onIsOverheatedChanged(mIsOverheated);
}
@Override
@@ -222,6 +228,13 @@
fireBatteryUnknownStateChanged();
}
+ int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+ boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
+ if (isOverheated != mIsOverheated) {
+ mIsOverheated = isOverheated;
+ fireIsOverheatedChanged();
+ }
+
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
@@ -292,6 +305,10 @@
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
+ public boolean isOverheated() {
+ return mIsOverheated;
+ }
+
@Override
public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
// Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -402,6 +419,15 @@
}
}
+ private void fireIsOverheatedChanged() {
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
+ }
+ }
+ }
+
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoModeController.isInDemoMode()) {
@@ -412,6 +438,7 @@
String plugged = args.getString("plugged");
String powerSave = args.getString("powersave");
String present = args.getString("present");
+ String overheated = args.getString("overheated");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -426,6 +453,10 @@
mStateUnknown = !present.equals("true");
fireBatteryUnknownStateChanged();
}
+ if (overheated != null) {
+ mIsOverheated = overheated.equals("true");
+ fireIsOverheatedChanged();
+ }
fireBatteryLevelChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index aae0f93..acdf0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -41,6 +40,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -83,6 +83,7 @@
@Inject
public BluetoothControllerImpl(
Context context,
+ UserTracker userTracker,
DumpManager dumpManager,
BluetoothLogger logger,
@Background Looper bgLooper,
@@ -100,7 +101,7 @@
mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
}
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mCurrentUser = ActivityManager.getCurrentUser();
+ mCurrentUser = userTracker.getUserId();
mDumpManager.registerDumpable(TAG, this);
}
@@ -145,7 +146,13 @@
}
private String getDeviceString(CachedBluetoothDevice device) {
- return device.getName() + " " + device.getBondState() + " " + device.isConnected();
+ return device.getName()
+ + " bondState=" + device.getBondState()
+ + " connected=" + device.isConnected()
+ + " active[A2DP]=" + device.isActiveDevice(BluetoothProfile.A2DP)
+ + " active[HEADSET]=" + device.isActiveDevice(BluetoothProfile.HEADSET)
+ + " active[HEARING_AID]=" + device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ + " active[LE_AUDIO]=" + device.isActiveDevice(BluetoothProfile.LE_AUDIO);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 576962d..d84cbcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -49,7 +50,7 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -79,7 +80,7 @@
private static final String SHOW_SECONDS = "show_seconds";
private static final String VISIBILITY = "visibility";
- private final CurrentUserTracker mCurrentUserTracker;
+ private final UserTracker mUserTracker;
private final CommandQueue mCommandQueue;
private int mCurrentUserId;
@@ -114,6 +115,14 @@
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mCurrentUserId = newUser;
+ }
+ };
+
public Clock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -132,12 +141,7 @@
a.recycle();
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
- mCurrentUserTracker = new CurrentUserTracker(mBroadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUserId = newUserId;
- }
- };
+ mUserTracker = Dependency.get(UserTracker.class);
}
@Override
@@ -196,8 +200,8 @@
Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_HIDE_LIST);
mCommandQueue.addCallback(this);
- mCurrentUserTracker.startTracking();
- mCurrentUserId = mCurrentUserTracker.getCurrentUserId();
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mCurrentUserId = mUserTracker.getUserId();
}
// The time zone may have changed while the receiver wasn't registered, so update the Time
@@ -227,7 +231,7 @@
mAttached = false;
Dependency.get(TunerService.class).removeTunable(this);
mCommandQueue.removeCallback(this);
- mCurrentUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index 4c6c7e0..3d0e69c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -22,7 +22,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 69b55c8..a4821e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -18,7 +18,6 @@
import static android.net.TetheringManager.TETHERING_WIFI;
-import android.app.ActivityManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.TetheringManager;
@@ -38,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -59,6 +59,7 @@
private final WifiManager mWifiManager;
private final Handler mMainHandler;
private final Context mContext;
+ private final UserTracker mUserTracker;
private int mHotspotState;
private volatile int mNumConnectedDevices;
@@ -95,10 +96,12 @@
@Inject
public HotspotControllerImpl(
Context context,
+ UserTracker userTracker,
@Main Handler mainHandler,
@Background Handler backgroundHandler,
DumpManager dumpManager) {
mContext = context;
+ mUserTracker = userTracker;
mTetheringManager = context.getSystemService(TetheringManager.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mMainHandler = mainHandler;
@@ -125,7 +128,7 @@
@Override
public boolean isHotspotSupported() {
return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
- && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
+ && UserManager.get(mContext).isUserAdmin(mUserTracker.getUserId());
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index cc241d9..ba94714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.DeviceOwnerType;
@@ -55,8 +54,9 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import org.xmlpull.v1.XmlPullParserException;
@@ -70,7 +70,7 @@
/**
*/
@SysUISingleton
-public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
+public class SecurityControllerImpl implements SecurityController {
private static final String TAG = "SecurityController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -84,11 +84,13 @@
private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
private final Context mContext;
+ private final UserTracker mUserTracker;
private final ConnectivityManager mConnectivityManager;
private final VpnManager mVpnManager;
private final DevicePolicyManager mDevicePolicyManager;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
+ private final Executor mMainExecutor;
private final Executor mBgExecutor;
@GuardedBy("mCallbacks")
@@ -102,18 +104,28 @@
// Needs to be cached here since the query has to be asynchronous
private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ onUserSwitched(newUser);
+ }
+ };
+
/**
*/
@Inject
public SecurityControllerImpl(
Context context,
+ UserTracker userTracker,
@Background Handler bgHandler,
BroadcastDispatcher broadcastDispatcher,
+ @Main Executor mainExecutor,
@Background Executor bgExecutor,
DumpManager dumpManager
) {
- super(broadcastDispatcher);
mContext = context;
+ mUserTracker = userTracker;
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
@@ -121,6 +133,7 @@
mVpnManager = context.getSystemService(VpnManager.class);
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -133,8 +146,8 @@
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
- onUserSwitched(ActivityManager.getCurrentUser());
- startTracking();
+ onUserSwitched(mUserTracker.getUserId());
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 29285f8..a593d51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,7 +27,6 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
@@ -40,8 +38,11 @@
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -53,6 +54,7 @@
private static final String TAG = "UserInfoController";
private final Context mContext;
+ private final UserTracker mUserTracker;
private final ArrayList<OnUserInfoChangedListener> mCallbacks =
new ArrayList<OnUserInfoChangedListener>();
private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
@@ -64,11 +66,11 @@
/**
*/
@Inject
- public UserInfoControllerImpl(Context context) {
+ public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+ UserTracker userTracker) {
mContext = context;
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(mReceiver, filter);
+ mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
IntentFilter profileFilter = new IntentFilter();
profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
@@ -88,15 +90,13 @@
mCallbacks.remove(callback);
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- reloadUserInfo();
- }
- }
- };
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reloadUserInfo();
+ }
+ };
private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
@Override
@@ -104,15 +104,11 @@
final String action = intent.getAction();
if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
- try {
- final int currentUser = ActivityManager.getService().getCurrentUser().id;
- final int changedUser =
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
- if (changedUser == currentUser) {
- reloadUserInfo();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get current user id for profile change", e);
+ final int currentUser = mUserTracker.getUserId();
+ final int changedUser =
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+ if (changedUser == currentUser) {
+ reloadUserInfo();
}
}
}
@@ -130,15 +126,12 @@
Context currentUserContext;
UserInfo userInfo;
try {
- userInfo = ActivityManager.getService().getCurrentUser();
+ userInfo = mUserTracker.getUserInfo();
currentUserContext = mContext.createPackageContextAsUser("android", 0,
new UserHandle(userInfo.id));
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Couldn't create user context", e);
throw new RuntimeException(e);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get user info", e);
- throw new RuntimeException(e);
}
final int userId = userInfo.id;
final boolean isGuest = userInfo.isGuest();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 146b222..bdb656b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -14,35 +14,74 @@
* limitations under the License.
*
*/
+
package com.android.systemui.statusbar.policy
-import android.annotation.UserIdInt
+import android.content.Context
import android.content.Intent
import android.view.View
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import dagger.Lazy
+import java.io.PrintWriter
import java.lang.ref.WeakReference
-import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
-/** Defines interface for a class that provides user switching functionality and state. */
-interface UserSwitcherController : Dumpable {
+/** Access point into multi-user switching logic. */
+@Deprecated("Use UserInteractor or GuestUserInteractor instead.")
+@SysUISingleton
+class UserSwitcherController
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val userInteractorLazy: Lazy<UserInteractor>,
+ private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+ private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+ private val activityStarter: ActivityStarter,
+) {
+
+ /** Defines interface for classes that can be called back when the user is switched. */
+ fun interface UserSwitchCallback {
+ /** Notifies that the user has switched. */
+ fun onUserSwitched()
+ }
+
+ private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+ private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+ private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
+
+ private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
/** The current list of [UserRecord]. */
val users: ArrayList<UserRecord>
+ get() = userInteractor.userRecords.value
/** Whether the user switcher experience should use the simple experience. */
val isSimpleUserSwitcher: Boolean
-
- /** Require a view for jank detection */
- fun init(view: View)
+ get() = userInteractor.isSimpleUserSwitcher
/** The [UserRecord] of the current user or `null` when none. */
val currentUserRecord: UserRecord?
+ get() = userInteractor.selectedUserRecord.value
/** The name of the current user of the device or `null`, when none is selected. */
val currentUserName: String?
+ get() =
+ currentUserRecord?.let {
+ LegacyUserUiHelper.getUserRecordName(
+ context = applicationContext,
+ record = it,
+ isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+ isGuestUserResetting = userInteractor.isGuestUserResetting,
+ )
+ }
/**
* Notifies that a user has been selected.
@@ -55,34 +94,40 @@
* @param userId The ID of the user to switch to.
* @param dialogShower An optional [DialogShower] in case we need to show dialogs.
*/
- fun onUserSelected(userId: Int, dialogShower: DialogShower?)
-
- /** Whether it is allowed to add users while the device is locked. */
- val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+ fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
+ userInteractor.selectUser(userId, dialogShower)
+ }
/** Whether the guest user is configured to always be present on the device. */
val isGuestUserAutoCreated: Boolean
+ get() = userInteractor.isGuestUserAutoCreated
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean
-
- /** Creates and switches to the guest user. */
- fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
-
- /** Shows the add user dialog. */
- fun showAddUserDialog(dialogShower: DialogShower?)
-
- /** Starts an activity to add a supervised user to the device. */
- fun startSupervisedUserActivity()
-
- /** Notifies when the display density or font scale has changed. */
- fun onDensityOrFontScaleChanged()
+ get() = userInteractor.isGuestUserResetting
/** Registers an adapter to notify when the users change. */
- fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+ fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+ userInteractor.addCallback(
+ object : UserInteractor.UserCallback {
+ override fun isEvictable(): Boolean {
+ return adapter.get() == null
+ }
+
+ override fun onUserStateChanged() {
+ adapter.get()?.notifyDataSetChanged()
+ }
+ }
+ )
+ }
/** Notifies the item for a user has been clicked. */
- fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+ fun onUserListItemClicked(
+ record: UserRecord,
+ dialogShower: DialogShower?,
+ ) {
+ userInteractor.onRecordSelected(record, dialogShower)
+ }
/**
* Removes guest user and switches to target user. The guest must be the current user and its id
@@ -103,7 +148,12 @@
* @param targetUserId id of the user to switch to after guest is removed. If
* `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
*/
- fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+ fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+ userInteractor.removeGuestUser(
+ guestUserId = guestUserId,
+ targetUserId = targetUserId,
+ )
+ }
/**
* Exits guest user and switches to previous non-guest user. The guest must be the current user.
@@ -114,43 +164,58 @@
* @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
* only if its ephemeral, else keep guest
*/
- fun exitGuestUser(
- @UserIdInt guestUserId: Int,
- @UserIdInt targetUserId: Int,
- forceRemoveGuestOnExit: Boolean
- )
+ fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
+ userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+ }
/**
* Guarantee guest is present only if the device is provisioned. Otherwise, create a content
* observer to wait until the device is provisioned, then schedule the guest creation.
*/
- fun schedulePostBootGuestCreation()
+ fun schedulePostBootGuestCreation() {
+ guestUserInteractor.onDeviceBootCompleted()
+ }
/** Whether keyguard is showing. */
val isKeyguardShowing: Boolean
+ get() = keyguardInteractor.isKeyguardShowing()
/** Starts an activity with the given [Intent]. */
- fun startActivity(intent: Intent)
+ fun startActivity(intent: Intent) {
+ activityStarter.startActivity(intent, /* dismissShade= */ true)
+ }
/**
* Refreshes users from UserManager.
*
* The pictures are only loaded if they have not been loaded yet.
- *
- * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
*/
- fun refreshUsers(forcePictureLoadForId: Int)
+ fun refreshUsers() {
+ userInteractor.refreshUsers()
+ }
/** Adds a subscriber to when user switches. */
- fun addUserSwitchCallback(callback: UserSwitchCallback)
+ fun addUserSwitchCallback(callback: UserSwitchCallback) {
+ val interactorCallback =
+ object : UserInteractor.UserCallback {
+ override fun onUserStateChanged() {
+ callback.onUserSwitched()
+ }
+ }
+ callbackCompatMap[callback] = interactorCallback
+ userInteractor.addCallback(interactorCallback)
+ }
/** Removes a previously-added subscriber. */
- fun removeUserSwitchCallback(callback: UserSwitchCallback)
+ fun removeUserSwitchCallback(callback: UserSwitchCallback) {
+ val interactorCallback = callbackCompatMap.remove(callback)
+ if (interactorCallback != null) {
+ userInteractor.removeCallback(interactorCallback)
+ }
+ }
- /** Defines interface for classes that can be called back when the user is switched. */
- fun interface UserSwitchCallback {
- /** Notifies that the user has switched. */
- fun onUserSwitched()
+ fun dump(pw: PrintWriter, args: Array<out String>) {
+ userInteractor.dump(pw)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
deleted file mode 100644
index 935fc7f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ /dev/null
@@ -1,299 +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.statusbar.policy
-
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import dagger.Lazy
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Implementation of [UserSwitcherController]. */
-@SysUISingleton
-class UserSwitcherControllerImpl
-@Inject
-constructor(
- @Application private val applicationContext: Context,
- flags: FeatureFlags,
- @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
- private val userInteractorLazy: Lazy<UserInteractor>,
- private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
- private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
- private val activityStarter: ActivityStarter,
-) : UserSwitcherController {
-
- private val useInteractor: Boolean =
- flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
- !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
- private val _oldImpl: UserSwitcherControllerOldImpl
- get() = oldImpl.get()
- private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
- private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
- private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
-
- private val callbackCompatMap =
- mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
-
- private fun notSupported(): Nothing {
- error("Not supported in the new implementation!")
- }
-
- override val users: ArrayList<UserRecord>
- get() =
- if (useInteractor) {
- userInteractor.userRecords.value
- } else {
- _oldImpl.users
- }
-
- override val isSimpleUserSwitcher: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isSimpleUserSwitcher
- } else {
- _oldImpl.isSimpleUserSwitcher
- }
-
- override fun init(view: View) {
- if (!useInteractor) {
- _oldImpl.init(view)
- }
- }
-
- override val currentUserRecord: UserRecord?
- get() =
- if (useInteractor) {
- userInteractor.selectedUserRecord.value
- } else {
- _oldImpl.currentUserRecord
- }
-
- override val currentUserName: String?
- get() =
- if (useInteractor) {
- currentUserRecord?.let {
- LegacyUserUiHelper.getUserRecordName(
- context = applicationContext,
- record = it,
- isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
- isGuestUserResetting = userInteractor.isGuestUserResetting,
- )
- }
- } else {
- _oldImpl.currentUserName
- }
-
- override fun onUserSelected(
- userId: Int,
- dialogShower: UserSwitchDialogController.DialogShower?
- ) {
- if (useInteractor) {
- userInteractor.selectUser(userId, dialogShower)
- } else {
- _oldImpl.onUserSelected(userId, dialogShower)
- }
- }
-
- override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
- get() =
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.isAddUsersFromLockScreenEnabled
- }
-
- override val isGuestUserAutoCreated: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isGuestUserAutoCreated
- } else {
- _oldImpl.isGuestUserAutoCreated
- }
-
- override val isGuestUserResetting: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isGuestUserResetting
- } else {
- _oldImpl.isGuestUserResetting
- }
-
- override fun createAndSwitchToGuestUser(
- dialogShower: UserSwitchDialogController.DialogShower?,
- ) {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.createAndSwitchToGuestUser(dialogShower)
- }
- }
-
- override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.showAddUserDialog(dialogShower)
- }
- }
-
- override fun startSupervisedUserActivity() {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.startSupervisedUserActivity()
- }
- }
-
- override fun onDensityOrFontScaleChanged() {
- if (!useInteractor) {
- _oldImpl.onDensityOrFontScaleChanged()
- }
- }
-
- override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
- if (useInteractor) {
- userInteractor.addCallback(
- object : UserInteractor.UserCallback {
- override fun isEvictable(): Boolean {
- return adapter.get() == null
- }
-
- override fun onUserStateChanged() {
- adapter.get()?.notifyDataSetChanged()
- }
- }
- )
- } else {
- _oldImpl.addAdapter(adapter)
- }
- }
-
- override fun onUserListItemClicked(
- record: UserRecord,
- dialogShower: UserSwitchDialogController.DialogShower?,
- ) {
- if (useInteractor) {
- userInteractor.onRecordSelected(record, dialogShower)
- } else {
- _oldImpl.onUserListItemClicked(record, dialogShower)
- }
- }
-
- override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
- if (useInteractor) {
- userInteractor.removeGuestUser(
- guestUserId = guestUserId,
- targetUserId = targetUserId,
- )
- } else {
- _oldImpl.removeGuestUser(guestUserId, targetUserId)
- }
- }
-
- override fun exitGuestUser(
- guestUserId: Int,
- targetUserId: Int,
- forceRemoveGuestOnExit: Boolean
- ) {
- if (useInteractor) {
- userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
- } else {
- _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
- }
- }
-
- override fun schedulePostBootGuestCreation() {
- if (useInteractor) {
- guestUserInteractor.onDeviceBootCompleted()
- } else {
- _oldImpl.schedulePostBootGuestCreation()
- }
- }
-
- override val isKeyguardShowing: Boolean
- get() =
- if (useInteractor) {
- keyguardInteractor.isKeyguardShowing()
- } else {
- _oldImpl.isKeyguardShowing
- }
-
- override fun startActivity(intent: Intent) {
- if (useInteractor) {
- activityStarter.startActivity(intent, /* dismissShade= */ true)
- } else {
- _oldImpl.startActivity(intent)
- }
- }
-
- override fun refreshUsers(forcePictureLoadForId: Int) {
- if (useInteractor) {
- userInteractor.refreshUsers()
- } else {
- _oldImpl.refreshUsers(forcePictureLoadForId)
- }
- }
-
- override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (useInteractor) {
- val interactorCallback =
- object : UserInteractor.UserCallback {
- override fun onUserStateChanged() {
- callback.onUserSwitched()
- }
- }
- callbackCompatMap[callback] = interactorCallback
- userInteractor.addCallback(interactorCallback)
- } else {
- _oldImpl.addUserSwitchCallback(callback)
- }
- }
-
- override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (useInteractor) {
- val interactorCallback = callbackCompatMap.remove(callback)
- if (interactorCallback != null) {
- userInteractor.removeCallback(interactorCallback)
- }
- } else {
- _oldImpl.removeUserSwitchCallback(callback)
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- if (useInteractor) {
- userInteractor.dump(pw)
- } else {
- _oldImpl.dump(pw, args)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
deleted file mode 100644
index c294c37..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ /dev/null
@@ -1,1063 +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.policy;
-
-import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
-
-import android.annotation.UserIdInt;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyCallback;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.animation.DialogCuj;
-import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
-import com.android.systemui.user.shared.model.UserActionModel;
-import com.android.systemui.user.ui.dialog.AddUserDialog;
-import com.android.systemui.user.ui.dialog.ExitGuestDialog;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * Old implementation. Keeps a list of all users on the device for user switching.
- *
- * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
- * instead.
- */
-@Deprecated
-@SysUISingleton
-public class UserSwitcherControllerOldImpl implements UserSwitcherController {
-
- private static final String TAG = "UserSwitcherController";
- private static final boolean DEBUG = false;
- private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
- "lockscreenSimpleUserSwitcher";
- private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
-
- private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
-
- private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
- private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
-
- protected final Context mContext;
- protected final UserTracker mUserTracker;
- protected final UserManager mUserManager;
- private final ContentObserver mSettingsObserver;
- private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
- @VisibleForTesting
- final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
- @VisibleForTesting
- final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
- private final KeyguardStateController mKeyguardStateController;
- private final DeviceProvisionedController mDeviceProvisionedController;
- private final DevicePolicyManager mDevicePolicyManager;
- protected final Handler mHandler;
- private final ActivityStarter mActivityStarter;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final BroadcastSender mBroadcastSender;
- private final TelephonyListenerManager mTelephonyListenerManager;
- private final InteractionJankMonitor mInteractionJankMonitor;
- private final LatencyTracker mLatencyTracker;
- private final DialogLaunchAnimator mDialogLaunchAnimator;
-
- private ArrayList<UserRecord> mUsers = new ArrayList<>();
- @VisibleForTesting
- AlertDialog mExitGuestDialog;
- @VisibleForTesting
- Dialog mAddUserDialog;
- private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
- private boolean mSimpleUserSwitcher;
- // When false, there won't be any visual affordance to add a new user from the keyguard even if
- // the user is unlocked
- private final MutableStateFlow<Boolean> mAddUsersFromLockScreen =
- StateFlowKt.MutableStateFlow(false);
- private boolean mUserSwitcherEnabled;
- @VisibleForTesting
- boolean mPauseRefreshUsers;
- private int mSecondaryUser = UserHandle.USER_NULL;
- private Intent mSecondaryUserServiceIntent;
- private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
- private final UiEventLogger mUiEventLogger;
- private final IActivityManager mActivityManager;
- private final Executor mBgExecutor;
- private final Executor mUiExecutor;
- private final Executor mLongRunningExecutor;
- private final boolean mGuestUserAutoCreated;
- private final AtomicBoolean mGuestIsResetting;
- private final AtomicBoolean mGuestCreationScheduled;
- private FalsingManager mFalsingManager;
- @Nullable
- private View mView;
- private String mCreateSupervisedUserPackage;
- private GlobalSettings mGlobalSettings;
- private List<UserSwitchCallback> mUserSwitchCallbacks =
- Collections.synchronizedList(new ArrayList<>());
-
- @Inject
- public UserSwitcherControllerOldImpl(
- Context context,
- IActivityManager activityManager,
- UserManager userManager,
- UserTracker userTracker,
- KeyguardStateController keyguardStateController,
- DeviceProvisionedController deviceProvisionedController,
- DevicePolicyManager devicePolicyManager,
- @Main Handler handler,
- ActivityStarter activityStarter,
- BroadcastDispatcher broadcastDispatcher,
- BroadcastSender broadcastSender,
- UiEventLogger uiEventLogger,
- FalsingManager falsingManager,
- TelephonyListenerManager telephonyListenerManager,
- SecureSettings secureSettings,
- GlobalSettings globalSettings,
- @Background Executor bgExecutor,
- @LongRunning Executor longRunningExecutor,
- @Main Executor uiExecutor,
- InteractionJankMonitor interactionJankMonitor,
- LatencyTracker latencyTracker,
- DumpManager dumpManager,
- DialogLaunchAnimator dialogLaunchAnimator,
- GuestResumeSessionReceiver guestResumeSessionReceiver,
- GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
- mContext = context;
- mActivityManager = activityManager;
- mUserTracker = userTracker;
- mBroadcastDispatcher = broadcastDispatcher;
- mBroadcastSender = broadcastSender;
- mTelephonyListenerManager = telephonyListenerManager;
- mUiEventLogger = uiEventLogger;
- mFalsingManager = falsingManager;
- mInteractionJankMonitor = interactionJankMonitor;
- mLatencyTracker = latencyTracker;
- mGlobalSettings = globalSettings;
- mGuestResumeSessionReceiver = guestResumeSessionReceiver;
- mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
- mBgExecutor = bgExecutor;
- mLongRunningExecutor = longRunningExecutor;
- mUiExecutor = uiExecutor;
- mGuestResumeSessionReceiver.register();
- mGuestResetOrExitSessionReceiver.register();
- mGuestUserAutoCreated = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_guestUserAutoCreated);
- mGuestIsResetting = new AtomicBoolean();
- mGuestCreationScheduled = new AtomicBoolean();
- mKeyguardStateController = keyguardStateController;
- mDeviceProvisionedController = deviceProvisionedController;
- mDevicePolicyManager = devicePolicyManager;
- mHandler = handler;
- mActivityStarter = activityStarter;
- mUserManager = userManager;
- mDialogLaunchAnimator = dialogLaunchAnimator;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_REMOVED);
- filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_STOPPED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- mBroadcastDispatcher.registerReceiver(
- mReceiver, filter, null /* executor */,
- UserHandle.SYSTEM, Context.RECEIVER_EXPORTED, null /* permission */);
-
- mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-
- mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
-
- filter = new IntentFilter();
- mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
- PERMISSION_SELF, null /* scheduler */,
- Context.RECEIVER_EXPORTED_UNAUDITED);
-
- mSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
- mAddUsersFromLockScreen.setValue(
- mGlobalSettings.getIntForUser(
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- 0,
- UserHandle.USER_SYSTEM) != 0);
- mUserSwitcherEnabled = mGlobalSettings.getIntForUser(
- Settings.Global.USER_SWITCHER_ENABLED, 0, UserHandle.USER_SYSTEM) != 0;
- refreshUsers(UserHandle.USER_NULL);
- };
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED),
- true, mSettingsObserver);
- // Fetch initial values.
- mSettingsObserver.onChange(false);
-
- keyguardStateController.addCallback(mCallback);
- listenForCallState();
-
- mCreateSupervisedUserPackage = mContext.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage);
-
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
- refreshUsers(UserHandle.USER_NULL);
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void refreshUsers(int forcePictureLoadForId) {
- if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
- if (forcePictureLoadForId != UserHandle.USER_NULL) {
- mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
- }
-
- if (mPauseRefreshUsers) {
- return;
- }
-
- boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
- SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
- final int userCount = mUsers.size();
- for (int i = 0; i < userCount; i++) {
- UserRecord r = mUsers.get(i);
- if (r == null || r.picture == null || r.info == null || forceAllUsers
- || mForcePictureLoadForUserId.get(r.info.id)) {
- continue;
- }
- bitmaps.put(r.info.id, r.picture);
- }
- mForcePictureLoadForUserId.clear();
-
- mBgExecutor.execute(() -> {
- List<UserInfo> infos = mUserManager.getAliveUsers();
- if (infos == null) {
- return;
- }
- ArrayList<UserRecord> records = new ArrayList<>(infos.size());
- int currentId = mUserTracker.getUserId();
- // Check user switchability of the foreground user since SystemUI is running in
- // User 0
- boolean canSwitchUsers = mUserManager.getUserSwitchability(
- UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
- UserRecord guestRecord = null;
-
- for (UserInfo info : infos) {
- boolean isCurrent = currentId == info.id;
- if (!mUserSwitcherEnabled && !info.isPrimary()) {
- continue;
- }
-
- if (info.isEnabled()) {
- if (info.isGuest()) {
- // Tapping guest icon triggers remove and a user switch therefore
- // the icon shouldn't be enabled even if the user is current
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- mUserManager,
- null /* picture */,
- info,
- isCurrent,
- canSwitchUsers);
- } else if (info.supportsSwitchToByUser()) {
- records.add(
- LegacyUserDataHelper.createRecord(
- mContext,
- mUserManager,
- bitmaps.get(info.id),
- info,
- isCurrent,
- canSwitchUsers));
- }
- }
- }
-
- if (guestRecord == null) {
- if (mGuestUserAutoCreated) {
- // If mGuestIsResetting=true, the switch should be disabled since
- // we will just use it as an indicator for "Resetting guest...".
- // Otherwise, default to canSwitchUsers.
- boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ENTER_GUEST_MODE,
- false /* isRestricted */,
- isSwitchToGuestEnabled);
- records.add(guestRecord);
- } else if (canCreateGuest(guestRecord != null)) {
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ENTER_GUEST_MODE,
- false /* isRestricted */,
- canSwitchUsers);
- records.add(guestRecord);
- }
- } else {
- records.add(guestRecord);
- }
-
- if (canCreateUser()) {
- final UserRecord userRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ADD_USER,
- createIsRestricted(),
- canSwitchUsers);
- records.add(userRecord);
- }
-
- if (canCreateSupervisedUser()) {
- final UserRecord userRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ADD_SUPERVISED_USER,
- createIsRestricted(),
- canSwitchUsers);
- records.add(userRecord);
- }
-
- if (canManageUsers()) {
- records.add(LegacyUserDataHelper.createRecord(
- mContext,
- KeyguardUpdateMonitor.getCurrentUser(),
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- /* isRestricted= */ false,
- /* isSwitchToEnabled= */ true
- ));
- }
-
- mUiExecutor.execute(() -> {
- if (records != null) {
- mUsers = records;
- notifyAdapters();
- }
- });
- });
- }
-
- private boolean systemCanCreateUsers() {
- return !mUserManager.hasBaseUserRestriction(
- UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
- }
-
- private boolean currentUserCanCreateUsers() {
- UserInfo currentUser = mUserTracker.getUserInfo();
- return currentUser != null
- && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
- && systemCanCreateUsers();
- }
-
- private boolean anyoneCanCreateUsers() {
- return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
- }
-
- @VisibleForTesting
- boolean canCreateGuest(boolean hasExistingGuest) {
- return mUserSwitcherEnabled
- && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
- && !hasExistingGuest;
- }
-
- @VisibleForTesting
- boolean canCreateUser() {
- return mUserSwitcherEnabled
- && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
- && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
- }
-
- @VisibleForTesting
- boolean canManageUsers() {
- UserInfo currentUser = mUserTracker.getUserInfo();
- return mUserSwitcherEnabled
- && ((currentUser != null && currentUser.isAdmin())
- || mAddUsersFromLockScreen.getValue());
- }
-
- private boolean createIsRestricted() {
- return !mAddUsersFromLockScreen.getValue();
- }
-
- @VisibleForTesting
- boolean canCreateSupervisedUser() {
- return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
- }
-
- private void pauseRefreshUsers() {
- if (!mPauseRefreshUsers) {
- mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
- mPauseRefreshUsers = true;
- }
- }
-
- private void notifyAdapters() {
- for (int i = mAdapters.size() - 1; i >= 0; i--) {
- BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
- if (adapter != null) {
- adapter.notifyDataSetChanged();
- } else {
- mAdapters.remove(i);
- }
- }
- }
-
- @Override
- public boolean isSimpleUserSwitcher() {
- return mSimpleUserSwitcher;
- }
-
- /**
- * Returns whether the current user is a system user.
- */
- @VisibleForTesting
- boolean isSystemUser() {
- return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
- }
-
- @Override
- public @Nullable UserRecord getCurrentUserRecord() {
- for (int i = 0; i < mUsers.size(); ++i) {
- UserRecord userRecord = mUsers.get(i);
- if (userRecord.isCurrent) {
- return userRecord;
- }
- }
- return null;
- }
-
- @Override
- public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
- UserRecord userRecord = mUsers.stream()
- .filter(x -> x.resolveId() == userId)
- .findFirst()
- .orElse(null);
- if (userRecord == null) {
- return;
- }
-
- onUserListItemClicked(userRecord, dialogShower);
- }
-
- @Override
- public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
- return mAddUsersFromLockScreen;
- }
-
- @Override
- public boolean isGuestUserAutoCreated() {
- return mGuestUserAutoCreated;
- }
-
- @Override
- public boolean isGuestUserResetting() {
- return mGuestIsResetting.get();
- }
-
- @Override
- public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
- if (record.isGuest && record.info == null) {
- createAndSwitchToGuestUser(dialogShower);
- } else if (record.isAddUser) {
- showAddUserDialog(dialogShower);
- } else if (record.isAddSupervisedUser) {
- startSupervisedUserActivity();
- } else if (record.isManageUsers) {
- startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
- } else {
- onUserListItemClicked(record.info.id, record, dialogShower);
- }
- }
-
- private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
- int currUserId = mUserTracker.getUserId();
- // If switching from guest and guest is ephemeral, then follow the flow
- // of showExitGuestDialog to remove current guest,
- // and switch to selected user
- UserInfo currUserInfo = mUserTracker.getUserInfo();
- if (currUserId == id) {
- if (record.isGuest) {
- showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
- }
- return;
- }
-
- if (currUserInfo != null && currUserInfo.isGuest()) {
- showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
- record.resolveId(), dialogShower);
- return;
- }
-
- if (dialogShower != null) {
- // If we haven't morphed into another dialog, it means we have just switched users.
- // Then, dismiss the dialog.
- dialogShower.dismiss();
- }
- switchToUserId(id);
- }
-
- private void switchToUserId(int id) {
- try {
- if (mView != null) {
- mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
- .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
- .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
- }
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
- pauseRefreshUsers();
- mActivityManager.switchUser(id);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't switch user.", e);
- }
- }
-
- private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
- int newId = UserHandle.USER_SYSTEM;
- if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
- UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
- if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
- newId = info.id;
- }
- }
- showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
- }
-
- private void showExitGuestDialog(
- int id,
- boolean isGuestEphemeral,
- int targetId,
- DialogShower dialogShower) {
- if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
- mExitGuestDialog.cancel();
- }
- mExitGuestDialog = new ExitGuestDialog(
- mContext,
- id,
- isGuestEphemeral,
- targetId,
- mKeyguardStateController.isShowing(),
- mFalsingManager,
- mDialogLaunchAnimator,
- this::exitGuestUser);
- if (dialogShower != null) {
- dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
- InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
- INTERACTION_JANK_EXIT_GUEST_MODE_TAG));
- } else {
- mExitGuestDialog.show();
- }
- }
-
- @Override
- public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
- createGuestAsync(guestId -> {
- // guestId may be USER_NULL if we haven't reloaded the user list yet.
- if (guestId != UserHandle.USER_NULL) {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
- onUserListItemClicked(guestId, UserRecord.createForGuest(), dialogShower);
- }
- });
- }
-
- @Override
- public void showAddUserDialog(@Nullable DialogShower dialogShower) {
- if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
- mAddUserDialog.cancel();
- }
- final UserInfo currentUser = mUserTracker.getUserInfo();
- mAddUserDialog = new AddUserDialog(
- mContext,
- currentUser.getUserHandle(),
- mKeyguardStateController.isShowing(),
- /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
- mFalsingManager,
- mBroadcastSender,
- mDialogLaunchAnimator);
- if (dialogShower != null) {
- dialogShower.showDialog(mAddUserDialog,
- new DialogCuj(
- InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
- INTERACTION_JANK_ADD_NEW_USER_TAG
- ));
- } else {
- mAddUserDialog.show();
- }
- }
-
- @Override
- public void startSupervisedUserActivity() {
- final Intent intent = new Intent()
- .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
- .setPackage(mCreateSupervisedUserPackage)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- mContext.startActivity(intent);
- }
-
- private void listenForCallState() {
- mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
- }
-
- private final TelephonyCallback.CallStateListener mPhoneStateListener =
- new TelephonyCallback.CallStateListener() {
- private int mCallState;
-
- @Override
- public void onCallStateChanged(int state) {
- if (mCallState == state) return;
- if (DEBUG) Log.v(TAG, "Call state changed: " + state);
- mCallState = state;
- refreshUsers(UserHandle.USER_NULL);
- }
- };
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.v(TAG, "Broadcast: a=" + intent.getAction()
- + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
- }
-
- boolean unpauseRefreshUsers = false;
- int forcePictureLoadForId = UserHandle.USER_NULL;
-
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
- mExitGuestDialog.cancel();
- mExitGuestDialog = null;
- }
-
- final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- final UserInfo userInfo = mUserManager.getUserInfo(currentId);
- final int userCount = mUsers.size();
- for (int i = 0; i < userCount; i++) {
- UserRecord record = mUsers.get(i);
- if (record.info == null) continue;
- boolean shouldBeCurrent = record.info.id == currentId;
- if (record.isCurrent != shouldBeCurrent) {
- mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
- }
- if (shouldBeCurrent && !record.isGuest) {
- mLastNonGuestUser = record.info.id;
- }
- if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
- // Immediately remove restricted records in case the AsyncTask is too slow.
- mUsers.remove(i);
- i--;
- }
- }
- notifyUserSwitchCallbacks();
- notifyAdapters();
-
- // Disconnect from the old secondary user's service
- if (mSecondaryUser != UserHandle.USER_NULL) {
- context.stopServiceAsUser(mSecondaryUserServiceIntent,
- UserHandle.of(mSecondaryUser));
- mSecondaryUser = UserHandle.USER_NULL;
- }
- // Connect to the new secondary user's service (purely to ensure that a persistent
- // SystemUI application is created for that user)
- if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) {
- context.startServiceAsUser(mSecondaryUserServiceIntent,
- UserHandle.of(userInfo.id));
- mSecondaryUser = userInfo.id;
- }
- unpauseRefreshUsers = true;
- if (mGuestUserAutoCreated) {
- // Guest user must be scheduled for creation AFTER switching to the target user.
- // This avoids lock contention which will produce UX bugs on the keyguard
- // (b/193933686).
- // TODO(b/191067027): Move guest user recreation to system_server
- guaranteeGuestPresent();
- }
- } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
- forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL);
- } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- // Unlocking the system user may require a refresh
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId != UserHandle.USER_SYSTEM) {
- return;
- }
- }
- refreshUsers(forcePictureLoadForId);
- if (unpauseRefreshUsers) {
- mUnpauseRefreshUsers.run();
- }
- }
- };
-
- private final Runnable mUnpauseRefreshUsers = new Runnable() {
- @Override
- public void run() {
- mHandler.removeCallbacks(this);
- mPauseRefreshUsers = false;
- refreshUsers(UserHandle.USER_NULL);
- }
- };
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("UserSwitcherController state:");
- pw.println(" mLastNonGuestUser=" + mLastNonGuestUser);
- pw.print(" mUsers.size="); pw.println(mUsers.size());
- for (int i = 0; i < mUsers.size(); i++) {
- final UserRecord u = mUsers.get(i);
- pw.print(" "); pw.println(u.toString());
- }
- pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher);
- pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
- }
-
- @Override
- public String getCurrentUserName() {
- if (mUsers.isEmpty()) return null;
- UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
- if (item == null || item.info == null) return null;
- if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name);
- return item.info.name;
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- refreshUsers(UserHandle.USER_ALL);
- }
-
- @Override
- public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
- mAdapters.add(adapter);
- }
-
- @Override
- public ArrayList<UserRecord> getUsers() {
- return mUsers;
- }
-
- @Override
- public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (currentUser.id != guestUserId) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not current user (" + currentUser.id + ")");
- return;
- }
- if (!currentUser.isGuest()) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not a guest");
- return;
- }
-
- boolean marked = mUserManager.markGuestForDeletion(currentUser.id);
- if (!marked) {
- Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
- return;
- }
-
- if (targetUserId == UserHandle.USER_NULL) {
- // Create a new guest in the foreground, and then immediately switch to it
- createGuestAsync(newGuestId -> {
- if (newGuestId == UserHandle.USER_NULL) {
- Log.e(TAG, "Could not create new guest, switching back to system user");
- switchToUserId(UserHandle.USER_SYSTEM);
- mUserManager.removeUser(currentUser.id);
- try {
- WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't remove guest because ActivityManager "
- + "or WindowManager is dead");
- }
- return;
- }
- switchToUserId(newGuestId);
- mUserManager.removeUser(currentUser.id);
- });
- } else {
- if (mGuestUserAutoCreated) {
- mGuestIsResetting.set(true);
- }
- switchToUserId(targetUserId);
- mUserManager.removeUser(currentUser.id);
- }
- }
-
- @Override
- public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
- boolean forceRemoveGuestOnExit) {
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (currentUser.id != guestUserId) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not current user (" + currentUser.id + ")");
- return;
- }
- if (!currentUser.isGuest()) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not a guest");
- return;
- }
-
- int newUserId = UserHandle.USER_SYSTEM;
- if (targetUserId == UserHandle.USER_NULL) {
- // when target user is not specified switch to last non guest user
- if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
- UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
- if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
- newUserId = info.id;
- }
- }
- } else {
- newUserId = targetUserId;
- }
-
- if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
- removeGuestUser(currentUser.id, newUserId);
- } else {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
- switchToUserId(newUserId);
- }
- }
-
- private void scheduleGuestCreation() {
- if (!mGuestCreationScheduled.compareAndSet(false, true)) {
- return;
- }
-
- mLongRunningExecutor.execute(() -> {
- int newGuestId = createGuest();
- mGuestCreationScheduled.set(false);
- mGuestIsResetting.set(false);
- if (newGuestId == UserHandle.USER_NULL) {
- Log.w(TAG, "Could not create new guest while exiting existing guest");
- // Refresh users so that we still display "Guest" if
- // config_guestUserAutoCreated=true
- refreshUsers(UserHandle.USER_NULL);
- }
- });
-
- }
-
- @Override
- public void schedulePostBootGuestCreation() {
- if (isDeviceAllowedToAddGuest()) {
- guaranteeGuestPresent();
- } else {
- mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
- }
- }
-
- private boolean isDeviceAllowedToAddGuest() {
- return mDeviceProvisionedController.isDeviceProvisioned()
- && !mDevicePolicyManager.isDeviceManaged();
- }
-
- /**
- * If there is no guest on the device, schedule creation of a new guest user in the background.
- */
- private void guaranteeGuestPresent() {
- if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
- scheduleGuestCreation();
- }
- }
-
- private void createGuestAsync(Consumer<Integer> callback) {
- final Dialog guestCreationProgressDialog =
- new UserCreatingDialog(mContext, /* isGuest= */true);
- guestCreationProgressDialog.show();
-
- // userManager.createGuest will block the thread so post is needed for the dialog to show
- mBgExecutor.execute(() -> {
- final int guestId = createGuest();
- mUiExecutor.execute(() -> {
- guestCreationProgressDialog.dismiss();
- if (guestId == UserHandle.USER_NULL) {
- Toast.makeText(mContext,
- com.android.settingslib.R.string.add_guest_failed,
- Toast.LENGTH_SHORT).show();
- }
- callback.accept(guestId);
- });
- });
- }
-
- /**
- * Creates a guest user and return its multi-user user ID.
- *
- * This method does not check if a guest already exists before it makes a call to
- * {@link UserManager} to create a new one.
- *
- * @return The multi-user user ID of the newly created guest user, or
- * {@link UserHandle#USER_NULL} if the guest couldn't be created.
- */
- private @UserIdInt int createGuest() {
- UserInfo guest;
- try {
- guest = mUserManager.createGuest(mContext);
- } catch (UserManager.UserOperationException e) {
- Log.e(TAG, "Couldn't create guest user", e);
- return UserHandle.USER_NULL;
- }
- if (guest == null) {
- Log.e(TAG, "Couldn't create guest, most likely because there already exists one");
- return UserHandle.USER_NULL;
- }
- return guest.id;
- }
-
- @Override
- public void init(View view) {
- mView = view;
- }
-
- @Override
- public boolean isKeyguardShowing() {
- return mKeyguardStateController.isShowing();
- }
-
- private boolean shouldUseSimpleUserSwitcher() {
- int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
- return mGlobalSettings.getIntForUser(SIMPLE_USER_SWITCHER_GLOBAL_SETTING,
- defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
- }
-
- @Override
- public void startActivity(Intent intent) {
- mActivityStarter.startActivity(intent, /* dismissShade= */ true);
- }
-
- @Override
- public void addUserSwitchCallback(UserSwitchCallback callback) {
- mUserSwitchCallbacks.add(callback);
- }
-
- @Override
- public void removeUserSwitchCallback(UserSwitchCallback callback) {
- mUserSwitchCallbacks.remove(callback);
- }
-
- /**
- * Notify user switch callbacks that user has switched.
- */
- private void notifyUserSwitchCallbacks() {
- List<UserSwitchCallback> temp;
- synchronized (mUserSwitchCallbacks) {
- temp = new ArrayList<>(mUserSwitchCallbacks);
- }
- for (UserSwitchCallback callback : temp) {
- callback.onUserSwitched();
- }
- }
-
- private final KeyguardStateController.Callback mCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
-
- // When Keyguard is going away, we don't need to update our items immediately
- // which
- // helps making the transition faster.
- if (!mKeyguardStateController.isShowing()) {
- mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
- } else {
- notifyAdapters();
- }
- }
- };
-
- private final DeviceProvisionedController.DeviceProvisionedListener
- mGuaranteeGuestPresentAfterProvisioned =
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- if (isDeviceAllowedToAddGuest()) {
- mBgExecutor.execute(
- () -> mDeviceProvisionedController.removeCallback(
- mGuaranteeGuestPresentAfterProvisioned));
- guaranteeGuestPresent();
- }
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 9866389..b135d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -28,6 +27,7 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
@@ -46,7 +46,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.qs.SettingObserver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.GlobalSettings;
@@ -58,14 +58,15 @@
/** Platform implementation of the zen mode controller. **/
@SysUISingleton
-public class ZenModeControllerImpl extends CurrentUserTracker
- implements ZenModeController, Dumpable {
+public class ZenModeControllerImpl implements ZenModeController, Dumpable {
private static final String TAG = "ZenModeController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Object mCallbacksLock = new Object();
private final Context mContext;
+ private final UserTracker mUserTracker;
+ private final BroadcastDispatcher mBroadcastDispatcher;
private final SettingObserver mModeSetting;
private final SettingObserver mConfigSetting;
private final NotificationManager mNoMan;
@@ -80,23 +81,45 @@
private long mZenUpdateTime;
private NotificationManager.Policy mConsolidatedNotificationPolicy;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, Context userContext) {
+ mUserId = newUser;
+ if (mRegistered) {
+ mBroadcastDispatcher.unregisterReceiver(mReceiver);
+ }
+ final IntentFilter filter = new IntentFilter(
+ AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter, null,
+ UserHandle.of(mUserId));
+ mRegistered = true;
+ mSetupObserver.register();
+ }
+ };
+
@Inject
public ZenModeControllerImpl(
Context context,
@Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
DumpManager dumpManager,
- GlobalSettings globalSettings) {
- super(broadcastDispatcher);
+ GlobalSettings globalSettings,
+ UserTracker userTracker) {
mContext = context;
- mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE) {
+ mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
+ mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateZenMode(value);
fireZenChanged(value);
}
};
- mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG) {
+ mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateZenModeConfig();
@@ -112,7 +135,7 @@
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
@@ -120,7 +143,7 @@
@Override
public boolean isVolumeRestricted() {
return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
- new UserHandle(mUserId));
+ UserHandle.of(mUserId));
}
@Override
@@ -183,19 +206,6 @@
}
@Override
- public void onUserSwitched(int userId) {
- mUserId = userId;
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- }
- final IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId), filter, null, null);
- mRegistered = true;
- mSetupObserver.register();
- }
-
- @Override
public ComponentName getEffectsSuppressor() {
return NotificationManager.from(mContext).getEffectsSuppressor();
}
@@ -208,7 +218,7 @@
@Override
public int getCurrentUser() {
- return ActivityManager.getCurrentUser();
+ return mUserTracker.getUserId();
}
private void fireNextAlarmChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b1b45b5..1b73539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,8 +58,6 @@
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.statusbar.policy.WalletControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -198,8 +196,4 @@
static DataSaverController provideDataSaverController(NetworkController networkController) {
return networkController.getDataSaverController();
}
-
- /** Binds {@link UserSwitcherController} to its implementation. */
- @Binds
- UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
index 48df15c..93e78ac 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import androidx.annotation.VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index c7f0b7e..f558fee 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.content.Context
import android.graphics.Canvas
@@ -31,11 +31,21 @@
class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
internal val ripples = ArrayList<RippleAnimation>()
+ private val listeners = ArrayList<RipplesFinishedListener>()
private val ripplePaint = Paint()
private var isWarningLogged = false
companion object {
- const val TAG = "MultiRippleView"
+ private const val TAG = "MultiRippleView"
+
+ interface RipplesFinishedListener {
+ /** Triggered when all the ripples finish running. */
+ fun onRipplesFinish()
+ }
+ }
+
+ fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
+ listeners.add(listener)
}
override fun onDraw(canvas: Canvas?) {
@@ -62,6 +72,10 @@
shouldInvalidate = shouldInvalidate || anim.isPlaying()
}
- if (shouldInvalidate) invalidate()
+ if (shouldInvalidate) {
+ invalidate()
+ } else { // Nothing is playing.
+ listeners.forEach { listener -> listener.onRipplesFinish() }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index aca9e25..b2f8994 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 8812254..773ac55 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
@@ -27,6 +27,6 @@
companion object {
const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
- const val RIPPLE_DEFAULT_ALPHA: Int = 45 // full opacity is 255.
+ const val RIPPLE_DEFAULT_ALPHA: Int = 115 // full opacity is 255.
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index d2f3a6a..a950d34 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.PointF
import android.graphics.RuntimeShader
import android.util.MathUtils
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
/**
* Shader class that renders an expanding ripple effect. The ripple contains three elements:
@@ -31,7 +33,7 @@
* Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
*/
class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
- RuntimeShader(buildShader(rippleShape)) {
+ RuntimeShader(buildShader(rippleShape)) {
/** Shapes that the [RippleShader] supports. */
enum class RippleShape {
@@ -39,25 +41,30 @@
ROUNDED_BOX,
ELLIPSE
}
- //language=AGSL
+ // language=AGSL
companion object {
- private const val SHADER_UNIFORMS = """uniform vec2 in_center;
- uniform vec2 in_size;
- uniform float in_progress;
- uniform float in_cornerRadius;
- uniform float in_thickness;
- uniform float in_time;
- uniform float in_distort_radial;
- uniform float in_distort_xy;
- uniform float in_fadeSparkle;
- uniform float in_fadeFill;
- uniform float in_fadeRing;
- uniform float in_blur;
- uniform float in_pixelDensity;
- layout(color) uniform vec4 in_color;
- uniform float in_sparkle_strength;"""
+ private const val SHADER_UNIFORMS =
+ """
+ uniform vec2 in_center;
+ uniform vec2 in_size;
+ uniform float in_progress;
+ uniform float in_cornerRadius;
+ uniform float in_thickness;
+ uniform float in_time;
+ uniform float in_distort_radial;
+ uniform float in_distort_xy;
+ uniform float in_fadeSparkle;
+ uniform float in_fadeFill;
+ uniform float in_fadeRing;
+ uniform float in_blur;
+ uniform float in_pixelDensity;
+ layout(color) uniform vec4 in_color;
+ uniform float in_sparkle_strength;
+ """
- private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_CIRCLE_MAIN =
+ """
+ vec4 main(vec2 p) {
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float radius = in_size.x * 0.5;
float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
@@ -73,7 +80,9 @@
}
"""
- private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_ROUNDED_BOX_MAIN =
+ """
+ vec4 main(vec2 p) {
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
in_thickness), in_blur);
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
@@ -89,7 +98,9 @@
}
"""
- private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_ELLIPSE_MAIN =
+ """
+ vec4 main(vec2 p) {
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
@@ -105,22 +116,31 @@
}
"""
- private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
- SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
+ private const val CIRCLE_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.CIRCLE_SDF +
SHADER_CIRCLE_MAIN
- private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
- RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
- SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
- private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
- SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
+ private const val ROUNDED_BOX_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ROUNDED_BOX_SDF +
+ SHADER_ROUNDED_BOX_MAIN
+ private const val ELLIPSE_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ELLIPSE_SDF +
SHADER_ELLIPSE_MAIN
private fun buildShader(rippleShape: RippleShape): String =
- when (rippleShape) {
- RippleShape.CIRCLE -> CIRCLE_SHADER
- RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
- RippleShape.ELLIPSE -> ELLIPSE_SHADER
- }
+ when (rippleShape) {
+ RippleShape.CIRCLE -> CIRCLE_SHADER
+ RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+ RippleShape.ELLIPSE -> ELLIPSE_SHADER
+ }
private fun subProgress(start: Float, end: Float, progress: Float): Float {
val min = Math.min(start, end)
@@ -130,9 +150,7 @@
}
}
- /**
- * Sets the center position of the ripple.
- */
+ /** Sets the center position of the ripple. */
fun setCenter(x: Float, y: Float) {
setFloatUniform("in_center", x, y)
}
@@ -144,21 +162,21 @@
maxSize.y = height
}
- /**
- * Progress of the ripple. Float value between [0, 1].
- */
+ /** Progress of the ripple. Float value between [0, 1]. */
var progress: Float = 0.0f
set(value) {
field = value
setFloatUniform("in_progress", value)
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
- setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
- /* height= */ maxSize.y * curvedProg)
+ setFloatUniform(
+ "in_size",
+ /* width= */ maxSize.x * curvedProg,
+ /* height= */ maxSize.y * curvedProg
+ )
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
// radius should not exceed width and height values.
- setFloatUniform("in_cornerRadius",
- Math.min(maxSize.x, maxSize.y) * curvedProg)
+ setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
@@ -175,18 +193,14 @@
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
}
- /**
- * Play time since the start of the effect.
- */
+ /** Play time since the start of the effect. */
var time: Float = 0.0f
set(value) {
field = value
setFloatUniform("in_time", value)
}
- /**
- * A hex value representing the ripple color, in the format of ARGB
- */
+ /** A hex value representing the ripple color, in the format of ARGB */
var color: Int = 0xffffff
set(value) {
field = value
@@ -194,9 +208,9 @@
}
/**
- * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus
- * with strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1
- * it's opaque white and looks the most grainy.
+ * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
+ * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
+ * opaque white and looks the most grainy.
*/
var sparkleStrength: Float = 0.0f
set(value) {
@@ -204,9 +218,7 @@
setFloatUniform("in_sparkle_strength", value)
}
- /**
- * Distortion strength of the ripple. Expected value between[0, 1].
- */
+ /** Distortion strength of the ripple. Expected value between[0, 1]. */
var distortionStrength: Float = 0.0f
set(value) {
field = value
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index a6d7930..ae28a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -26,7 +26,7 @@
import android.util.AttributeSet
import android.view.View
import androidx.core.graphics.ColorUtils
-import com.android.systemui.ripple.RippleShader.RippleShape
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
/**
* A generic expanding ripple effect.
@@ -41,7 +41,7 @@
private set
private val ripplePaint = Paint()
- private val animator = ValueAnimator.ofFloat(0f, 1f)
+ protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
var duration: Long = 1750
@@ -98,17 +98,20 @@
rippleShader.time = now.toFloat()
invalidate()
}
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- onAnimationEnd?.run()
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd?.run()
+ }
}
- })
+ )
animator.start()
}
- /** Set the color to be used for the ripple.
+ /**
+ * Set the color to be used for the ripple.
*
- * The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
+ * The alpha value of the color will be applied to the ripple. The alpha range is [0-255].
*/
fun setColor(color: Int, alpha: Int = RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA) {
rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
@@ -123,9 +126,7 @@
rippleShader.rippleFill = rippleFill
}
- /**
- * Set the intensity of the sparkles.
- */
+ /** Set the intensity of the sparkles. */
fun setSparkleStrength(strength: Float) {
rippleShader.sparkleStrength = strength
}
@@ -143,20 +144,30 @@
// active effect area. Values here should be kept in sync with the animation implementation
// in the ripple shader.
if (rippleShape == RippleShape.CIRCLE) {
- val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxWidth
+ val maskRadius =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
} else {
- val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxWidth * 2
- val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxHeight * 2
+ val maskWidth =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth * 2
+ val maskHeight =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxHeight * 2
canvas.drawRect(
- /* left= */ centerX - maskWidth,
- /* top= */ centerY - maskHeight,
- /* right= */ centerX + maskWidth,
- /* bottom= */ centerY + maskHeight,
- ripplePaint)
+ /* left= */ centerX - maskWidth,
+ /* top= */ centerY - maskHeight,
+ /* right= */ centerX + maskWidth,
+ /* bottom= */ centerY + maskHeight,
+ ripplePaint
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 5e256c6..8b2f466 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.shaderutil
/** Library class that contains 2D signed distance functions. */
class SdfShaderLibrary {
- //language=AGSL
+ // language=AGSL
companion object {
- const val CIRCLE_SDF = """
+ const val CIRCLE_SDF =
+ """
float sdCircle(vec2 p, float r) {
return (length(p)-r) / r;
}
@@ -34,7 +35,8 @@
}
"""
- const val ROUNDED_BOX_SDF = """
+ const val ROUNDED_BOX_SDF =
+ """
float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
size *= 0.5;
cornerRadius *= 0.5;
@@ -58,7 +60,8 @@
// Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
// This is more expensive than the regular circle SDF, recommend to use the circle SDF if
// possible.
- const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
+ const val ELLIPSE_SDF =
+ """float sdEllipse(vec2 p, vec2 wh) {
wh *= 0.5;
// symmetry
@@ -98,7 +101,8 @@
}
"""
- const val SHADER_SDF_OPERATION_LIB = """
+ const val SHADER_SDF_OPERATION_LIB =
+ """
float soften(float d, float blur) {
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
new file mode 100644
index 0000000..d78e0c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.surfaceeffects.shaderutil
+
+/** A common utility functions that are used for computing shaders. */
+class ShaderUtilLibrary {
+ // language=AGSL
+ companion object {
+ const val SHADER_LIB =
+ """
+ float triangleNoise(vec2 n) {
+ n = fract(n * vec2(5.3987, 5.4421));
+ n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+ float xy = n.x * n.y;
+ // compute in [0..2[ and remap to [-1.0..1.0[
+ return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+ }
+
+ const float PI = 3.1415926535897932384626;
+
+ float sparkles(vec2 uv, float t) {
+ float n = triangleNoise(uv);
+ float s = 0.0;
+ for (float i = 0; i < 4; i += 1) {
+ float l = i * 0.01;
+ float h = l + 0.1;
+ float o = smoothstep(n - l, h, n);
+ o *= abs(sin(PI * o * (t + 0.55 * i)));
+ s += o;
+ }
+ return s;
+ }
+
+ vec2 distort(vec2 p, float time, float distort_amount_radial,
+ float distort_amount_xy) {
+ float angle = atan(p.y, p.x);
+ return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+ cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+ cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+ }
+
+ // Return range [-1, 1].
+ vec3 hash(vec3 p) {
+ p = fract(p * vec3(.3456, .1234, .9876));
+ p += dot(p, p.yxz + 43.21);
+ p = (p.xxy + p.yxx) * p.zyx;
+ return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+ }
+
+ // Skew factors (non-uniform).
+ const float SKEW = 0.3333333; // 1/3
+ const float UNSKEW = 0.1666667; // 1/6
+
+ // Return range roughly [-1,1].
+ // It's because the hash function (that returns a random gradient vector) returns
+ // different magnitude of vectors. Noise doesn't have to be in the precise range thus
+ // skipped normalize.
+ float simplex3d(vec3 p) {
+ // Skew the input coordinate, so that we get squashed cubical grid
+ vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
+
+ // Unskew back
+ vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
+
+ // Unskewed coordinate that is relative to p, to compute the noise contribution
+ // based on the distance.
+ vec3 c0 = p - u;
+
+ // We have six simplices (in this case tetrahedron, since we are in 3D) that we
+ // could possibly in.
+ // Here, we are finding the correct tetrahedron (simplex shape), and traverse its
+ // four vertices (c0..3) when computing noise contribution.
+ // The way we find them is by comparing c0's x,y,z values.
+ // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
+ // by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
+ // Same applies in 3D.
+ //
+ // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
+ // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
+ // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
+ // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
+ // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
+ // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
+ // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
+ //
+ // The rule is:
+ // * For offset1, set 1 at the max component, otherwise 0.
+ // * For offset2, set 0 at the min component, otherwise 1.
+ // * For offset3, set 1 for all.
+ //
+ // Encode x0-y0, y0-z0, z0-x0 in a vec3
+ vec3 en = c0 - c0.yzx;
+ // Each represents whether x0>y0, y0>z0, z0>x0
+ en = step(vec3(0.), en);
+ // en.zxy encodes z0>x0, x0>y0, y0>x0
+ vec3 offset1 = en * (1. - en.zxy); // find max
+ vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
+ vec3 offset3 = vec3(1.);
+
+ vec3 c1 = c0 - offset1 + UNSKEW;
+ vec3 c2 = c0 - offset2 + UNSKEW * 2.;
+ vec3 c3 = c0 - offset3 + UNSKEW * 3.;
+
+ // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
+ //
+ // First compute d^2, squared distance to the point.
+ vec4 w; // w = max(0, r^2 - d^2))
+ w.x = dot(c0, c0);
+ w.y = dot(c1, c1);
+ w.z = dot(c2, c2);
+ w.w = dot(c3, c3);
+
+ // Noise contribution should decay to zero before they cross the simplex boundary.
+ // Usually r^2 is 0.5 or 0.6;
+ // 0.5 ensures continuity but 0.6 increases the visual quality for the application
+ // where discontinuity isn't noticeable.
+ w = max(0.6 - w, 0.);
+
+ // Noise contribution from each point.
+ vec4 nc;
+ nc.x = dot(hash(s), c0);
+ nc.y = dot(hash(s + offset1), c1);
+ nc.z = dot(hash(s + offset2), c2);
+ nc.w = dot(hash(s + offset3), c3);
+
+ nc *= w*w*w*w;
+
+ // Add all the noise contributions.
+ // Should multiply by the possible max contribution to adjust the range in [-1,1].
+ return dot(vec4(32.), nc);
+ }
+ """
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
new file mode 100644
index 0000000..5ac3aad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+import android.graphics.BlendMode
+import android.graphics.Color
+
+/** Turbulence noise animation configuration. */
+data class TurbulenceNoiseAnimationConfig(
+ /** The number of grids that is used to generate noise. */
+ val gridCount: Float = DEFAULT_NOISE_GRID_COUNT,
+
+ /** Multiplier for the noise luma matte. Increase this for brighter effects. */
+ val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER,
+
+ /**
+ * Noise move speed variables.
+ *
+ * Its sign determines the direction; magnitude determines the speed. <ul>
+ * ```
+ * <li> [noiseMoveSpeedX] positive: right to left; negative: left to right.
+ * <li> [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom.
+ * <li> [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it
+ * to add turbulence in place.
+ * ```
+ * </ul>
+ */
+ val noiseMoveSpeedX: Float = 0f,
+ val noiseMoveSpeedY: Float = 0f,
+ val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
+
+ /** Color of the effect. */
+ var color: Int = DEFAULT_COLOR,
+ /** Background color of the effect. */
+ val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
+ val opacity: Int = DEFAULT_OPACITY,
+ val width: Float = 0f,
+ val height: Float = 0f,
+ val duration: Float = DEFAULT_NOISE_DURATION_IN_MILLIS,
+ val pixelDensity: Float = 1f,
+ val blendMode: BlendMode = DEFAULT_BLEND_MODE,
+ val onAnimationEnd: Runnable? = null
+) {
+ companion object {
+ const val DEFAULT_NOISE_DURATION_IN_MILLIS = 7500F
+ const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
+ const val DEFAULT_NOISE_GRID_COUNT = 1.2f
+ const val DEFAULT_NOISE_SPEED_Z = 0.3f
+ const val DEFAULT_OPACITY = 150 // full opacity is 255.
+ const val DEFAULT_COLOR = Color.WHITE
+ const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
+ val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
new file mode 100644
index 0000000..4c7e5f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+/** A controller that plays [TurbulenceNoiseView]. */
+class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
+ /** Updates the color of the noise. */
+ fun updateNoiseColor(color: Int) {
+ turbulenceNoiseView.updateColor(color)
+ }
+
+ // TODO: add cancel and/ or pause once design requirements become clear.
+ /** Plays [TurbulenceNoiseView] with the given config. */
+ fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) {
+ turbulenceNoiseView.play(turbulenceNoiseAnimationConfig)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
new file mode 100644
index 0000000..19c114d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+import java.lang.Float.max
+
+/** Shader that renders turbulence simplex noise, with no octave. */
+class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+ // language=AGSL
+ companion object {
+ private const val UNIFORMS =
+ """
+ uniform float in_gridNum;
+ uniform vec3 in_noiseMove;
+ uniform vec2 in_size;
+ uniform float in_aspectRatio;
+ uniform float in_opacity;
+ uniform float in_pixelDensity;
+ layout(color) uniform vec4 in_color;
+ layout(color) uniform vec4 in_backgroundColor;
+ """
+
+ private const val SHADER_LIB =
+ """
+ float getLuminosity(vec3 c) {
+ return 0.3*c.r + 0.59*c.g + 0.11*c.b;
+ }
+
+ vec3 maskLuminosity(vec3 dest, float lum) {
+ dest.rgb *= vec3(lum);
+ // Clip back into the legal range
+ dest = clamp(dest, vec3(0.), vec3(1.0));
+ return dest;
+ }
+ """
+
+ private const val MAIN_SHADER =
+ """
+ vec4 main(vec2 p) {
+ vec2 uv = p / in_size.xy;
+ uv.x *= in_aspectRatio;
+
+ vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+ float luma = simplex3d(noiseP) * in_opacity;
+ vec3 mask = maskLuminosity(in_color.rgb, luma);
+ vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+ // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+ // shader here as we are in gamma space.
+ float dither = triangleNoise(p * in_pixelDensity) / 255.;
+
+ // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
+ // multiply rgb with a to get the correct result.
+ color = (color + dither.rrr) * in_color.a;
+ return vec4(color, in_color.a);
+ }
+ """
+
+ private const val TURBULENCE_NOISE_SHADER =
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+ }
+
+ /** Sets the number of grid for generating noise. */
+ fun setGridCount(gridNumber: Float = 1.0f) {
+ setFloatUniform("in_gridNum", gridNumber)
+ }
+
+ /**
+ * Sets the pixel density of the screen.
+ *
+ * Used it for noise dithering.
+ */
+ fun setPixelDensity(pixelDensity: Float) {
+ setFloatUniform("in_pixelDensity", pixelDensity)
+ }
+
+ /** Sets the noise color of the effect. */
+ fun setColor(color: Int) {
+ setColorUniform("in_color", color)
+ }
+
+ /** Sets the background color of the effect. */
+ fun setBackgroundColor(color: Int) {
+ setColorUniform("in_backgroundColor", color)
+ }
+
+ /**
+ * Sets the opacity to achieve fade in/ out of the animation.
+ *
+ * Expected value range is [1, 0].
+ */
+ fun setOpacity(opacity: Float) {
+ setFloatUniform("in_opacity", opacity)
+ }
+
+ /** Sets the size of the shader. */
+ fun setSize(width: Float, height: Float) {
+ setFloatUniform("in_size", width, height)
+ setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
+ }
+
+ /** Sets noise move speed in x, y, and z direction. */
+ fun setNoiseMove(x: Float, y: Float, z: Float) {
+ setFloatUniform("in_noiseMove", x, y, z)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
new file mode 100644
index 0000000..8649d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.ColorUtils
+import java.util.Random
+import kotlin.math.sin
+
+/** View that renders turbulence noise effect. */
+class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+ companion object {
+ private const val MS_TO_SEC = 0.001f
+ private const val TWO_PI = Math.PI.toFloat() * 2f
+ }
+
+ @VisibleForTesting val turbulenceNoiseShader = TurbulenceNoiseShader()
+ private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
+ private val random = Random()
+ private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+ private var config: TurbulenceNoiseAnimationConfig? = null
+
+ val isPlaying: Boolean
+ get() = animator.isRunning
+
+ init {
+ // Only visible during the animation.
+ visibility = INVISIBLE
+ }
+
+ /** Updates the color during the animation. No-op if there's no animation playing. */
+ fun updateColor(color: Int) {
+ config?.let {
+ it.color = color
+ applyConfig(it)
+ }
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ if (canvas == null || !canvas.isHardwareAccelerated) {
+ // Drawing with the turbulence noise shader requires hardware acceleration, so skip
+ // if it's unsupported.
+ return
+ }
+
+ canvas.drawPaint(paint)
+ }
+
+ internal fun play(config: TurbulenceNoiseAnimationConfig) {
+ if (isPlaying) {
+ return // Ignore if the animation is playing.
+ }
+ visibility = VISIBLE
+ applyConfig(config)
+
+ // Add random offset to avoid same patterned noise.
+ val offsetX = random.nextFloat()
+ val offsetY = random.nextFloat()
+
+ animator.duration = config.duration.toLong()
+ animator.addUpdateListener { updateListener ->
+ val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+ // Remap [0,1] to [0, 2*PI]
+ val progress = TWO_PI * updateListener.animatedValue as Float
+
+ turbulenceNoiseShader.setNoiseMove(
+ offsetX + timeInSec * config.noiseMoveSpeedX,
+ offsetY + timeInSec * config.noiseMoveSpeedY,
+ timeInSec * config.noiseMoveSpeedZ
+ )
+
+ // Fade in and out the noise as the animation progress.
+ // TODO: replace it with a better curve
+ turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
+
+ invalidate()
+ }
+
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ visibility = INVISIBLE
+ config.onAnimationEnd?.run()
+ }
+ }
+ )
+ animator.start()
+ }
+
+ private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+ this.config = config
+ with(turbulenceNoiseShader) {
+ setGridCount(config.gridCount)
+ setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+ setBackgroundColor(config.backgroundColor)
+ setSize(config.width, config.height)
+ setPixelDensity(config.pixelDensity)
+ }
+ paint.blendMode = config.blendMode
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 4cb41f3..a9d05d1 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -65,8 +65,7 @@
height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -95,6 +94,13 @@
private var wakeReasonAcquired: String? = null
/**
+ * A stack of pairs of device id and temporary view info. This is used when there may be
+ * multiple devices in range, and we want to always display the chip for the most recently
+ * active device.
+ */
+ internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque()
+
+ /**
* Displays the view with the provided [newInfo].
*
* This method handles inflating and attaching the view, then delegates to [updateView] to
@@ -103,6 +109,12 @@
fun displayView(newInfo: T) {
val currentDisplayInfo = displayInfo
+ // Update our list of active devices by removing it if necessary, then adding back at the
+ // front of the list
+ val id = newInfo.id
+ val position = findAndRemoveFromActiveViewsList(id)
+ activeViews.addFirst(Pair(id, newInfo))
+
if (currentDisplayInfo != null &&
currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
// We're already displaying information in the correctly-titled window, so we just need
@@ -114,27 +126,37 @@
// We're already displaying information but that information is under a different
// window title. So, we need to remove the old window with the old title and add a
// new window with the new title.
- removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}")
+ removeView(
+ id,
+ removalReason = "New info has new window title: ${newInfo.windowTitle}"
+ )
}
// At this point, we're guaranteed to no longer be displaying a view.
// So, set up all our callbacks and inflate the view.
configurationController.addCallback(displayScaleListener)
- // Wake the screen if necessary so the user will see the view. (Per b/239426653, we want
- // the view to show over the dream state, so we should only wake up if the screen is
- // completely off.)
- if (!powerManager.isScreenOn) {
- wakeLock = wakeLockBuilder
+
+ wakeLock = if (!powerManager.isScreenOn) {
+ // If the screen is off, fully wake it so the user can see the view.
+ wakeLockBuilder
.setTag(newInfo.windowTitle)
.setLevelsAndFlags(
- PowerManager.FULL_WAKE_LOCK or
- PowerManager.ACQUIRE_CAUSES_WAKEUP
+ PowerManager.FULL_WAKE_LOCK or
+ PowerManager.ACQUIRE_CAUSES_WAKEUP
)
.build()
- wakeLock?.acquire(newInfo.wakeReason)
- wakeReasonAcquired = newInfo.wakeReason
+ } else {
+ // Per b/239426653, we want the view to show over the dream state.
+ // If the screen is on, using screen bright level will leave screen on the dream
+ // state but ensure the screen will not go off before wake lock is released.
+ wakeLockBuilder
+ .setTag(newInfo.windowTitle)
+ .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+ .build()
}
- logger.logViewAddition(newInfo.windowTitle)
+ wakeLock?.acquire(newInfo.wakeReason)
+ wakeReasonAcquired = newInfo.wakeReason
+ logger.logViewAddition(id, newInfo.windowTitle)
inflateAndUpdateView(newInfo)
}
@@ -145,9 +167,13 @@
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
)
- cancelViewTimeout?.run()
+
+ // Only cancel timeout of the most recent view displayed, as it will be reset.
+ if (position == 0) {
+ cancelViewTimeout?.run()
+ }
cancelViewTimeout = mainExecutor.executeDelayed(
- { removeView(REMOVAL_REASON_TIMEOUT) },
+ { removeView(id, REMOVAL_REASON_TIMEOUT) },
timeout.toLong()
)
}
@@ -190,28 +216,67 @@
}
/**
- * Hides the view.
+ * Hides the view given its [id].
*
+ * @param id the id of the device responsible of displaying the temp view.
* @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.)
*/
- fun removeView(removalReason: String) {
+ fun removeView(id: String, removalReason: String) {
val currentDisplayInfo = displayInfo ?: return
+ val removalPosition = findAndRemoveFromActiveViewsList(id)
+ if (removalPosition == null) {
+ logger.logViewRemovalIgnored(id, "view not found in the list")
+ return
+ }
+ if (removalPosition != 0) {
+ logger.logViewRemovalIgnored(id, "most recent view is being displayed.")
+ return
+ }
+ logger.logViewRemoval(id, removalReason)
+
+ val newViewToDisplay = if (activeViews.isEmpty()) {
+ null
+ } else {
+ activeViews[0].second
+ }
+
val currentView = currentDisplayInfo.view
animateViewOut(currentView) {
windowManager.removeView(currentView)
wakeLock?.release(wakeReasonAcquired)
}
- logger.logViewRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
// Re-set to null immediately (instead as part of the animation end runnable) so
- // that if a new view event comes in while this view is animating out, we still display the
- // new view appropriately.
+ // that if a new view event comes in while this view is animating out, we still display
+ // the new view appropriately.
displayInfo = null
// No need to time the view out since it's already gone
cancelViewTimeout?.run()
+
+ if (newViewToDisplay != null) {
+ mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY)
+ }
+ }
+
+ /**
+ * Finds and removes the active view with the given [id] from the stack, or null if there is no
+ * active view with that ID
+ *
+ * @param id that temporary view belonged to.
+ *
+ * @return index of the view in the stack , otherwise null.
+ */
+ private fun findAndRemoveFromActiveViewsList(id: String): Int? {
+ for (i in 0 until activeViews.size) {
+ if (activeViews[i].first == id) {
+ activeViews.removeAt(i)
+ return i
+ }
+ }
+ return null
}
/**
@@ -252,6 +317,7 @@
}
private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
+const val DISPLAY_VIEW_DELAY = 50L
private data class IconInfo(
val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index cbb5002..df83960 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -37,6 +37,11 @@
* disappears.
*/
open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
+
+ /**
+ * The id of the temporary view.
+ */
+ abstract val id: String
}
const val DEFAULT_TIMEOUT_MILLIS = 10000
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 428a104..133a384 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -24,13 +24,42 @@
internal val buffer: LogBuffer,
internal val tag: String,
) {
- /** Logs that we added the view in a window titled [windowTitle]. */
- fun logViewAddition(windowTitle: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = windowTitle }, { "View added. window=$str1" })
+ /** Logs that we added the view with the given [id] in a window titled [windowTitle]. */
+ fun logViewAddition(id: String, windowTitle: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = windowTitle
+ str2 = id
+ },
+ { "View added. window=$str1 id=$str2" }
+ )
}
- /** Logs that we removed the chip for the given [reason]. */
- fun logViewRemoval(reason: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" })
+ /** Logs that we removed the view with the given [id] for the given [reason]. */
+ fun logViewRemoval(id: String, reason: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = reason
+ str2 = id
+ },
+ { "View with id=$str2 is removed due to: $str1" }
+ )
+ }
+
+ /** Logs that we ignored removal of the view with the given [id]. */
+ fun logViewRemovalIgnored(id: String, reason: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = reason
+ str2 = id
+ },
+ { "Removal of view with id=$str2 is ignored because $str1" }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 44e5ce8..fb17b69 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -174,7 +174,7 @@
chipInnerView,
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_DURATION,
+ duration = ANIMATION_IN_DURATION,
includeMargins = true,
includeFadeIn = true,
// We can only request focus once the animation finishes.
@@ -187,7 +187,7 @@
view.requireViewById<ViewGroup>(R.id.chipbar_inner),
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_DURATION,
+ ANIMATION_OUT_DURATION,
includeMargins = true,
onAnimationEnd,
)
@@ -208,4 +208,5 @@
}
}
-private const val ANIMATION_DURATION = 500L
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 6237365..b92e0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -40,6 +40,7 @@
override val windowTitle: String,
override val wakeReason: String,
override val timeoutMs: Int,
+ override val id: String,
) : TemporaryViewInfo()
/** The possible items to display at the end of the chipbar. */
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index 26cc6ba3..f3b9cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -25,8 +25,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.ToastPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index fe183fc..4999515 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -38,9 +38,9 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginEnablerImpl;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginActionManager;
import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginPrefs;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 61eadeb..5ea4399 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -48,6 +48,7 @@
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -159,7 +160,8 @@
ConfigurationController configurationController,
@Main Handler handler,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ ShadeExpansionStateManager shadeExpansionStateManager) {
return new HeadsUpManagerPhone(
context,
headsUpManagerLogger,
@@ -170,7 +172,8 @@
configurationController,
handler,
accessibilityManagerWrapper,
- uiEventLogger
+ uiEventLogger,
+ shadeExpansionStateManager
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 0f06144..6216acd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -92,6 +92,7 @@
deviceStateManager.registerCallback(executor, FoldListener())
wakefulnessLifecycle.addObserver(this)
+ // TODO(b/254878364): remove this call to NPVC.getView()
centralSurfaces.notificationPanelViewController.view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
}
@@ -157,6 +158,7 @@
// We don't need to wait for the scrim as it is already displayed
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
+ // TODO(b/254878364): remove this call to NPVC.getView()
OneShotPreDrawListener.add(
centralSurfaces.notificationPanelViewController.view,
onReady
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 6ed3a09..d411e34 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.unfold
-import android.animation.ValueAnimator
+import android.content.ContentResolver
import android.content.Context
import android.graphics.PixelFormat
import android.hardware.devicestate.DeviceStateManager
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.LinearLightRevealEffect
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
@@ -52,6 +53,7 @@
constructor(
private val context: Context,
private val deviceStateManager: DeviceStateManager,
+ private val contentResolver: ContentResolver,
private val displayManager: DisplayManager,
private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@@ -117,7 +119,7 @@
Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
try {
// Add the view only if we are unfolding and this is the first screen on
- if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
+ if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
addView(onOverlayReady)
isUnfoldHandled = true
} else {
@@ -162,11 +164,10 @@
// blocker (turn on the brightness) only when the content is actually visible as it
// might be presented only in the next frame.
// See b/197538198
- transaction
- .setFrameTimelineVsync(vsyncId)
- .apply()
+ transaction.setFrameTimelineVsync(vsyncId).apply()
- transaction.setFrameTimelineVsync(vsyncId + 1)
+ transaction
+ .setFrameTimelineVsync(vsyncId + 1)
.addTransactionCommittedListener(backgroundExecutor) {
Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
callback.run()
@@ -218,8 +219,7 @@
}
private fun getUnfoldedDisplayInfo(): DisplayInfo =
- displayManager
- .displays
+ displayManager.displays
.asSequence()
.map { DisplayInfo().apply { it.getDisplayInfo(this) } }
.filter { it.type == Display.TYPE_INTERNAL }
@@ -266,5 +266,6 @@
isUnfoldHandled = false
}
this.isFolded = isFolded
- })
+ }
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index bf70673..095718b 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -46,6 +46,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.CoreStartable;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.util.NotificationChannels;
@@ -61,15 +62,24 @@
private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
private final Context mContext;
+ private final BroadcastDispatcher mBroadcastDispatcher;
// TODO: delay some notifications to avoid bumpy fast operations
- private NotificationManager mNotificationManager;
- private StorageManager mStorageManager;
+ private final NotificationManager mNotificationManager;
+ private final StorageManager mStorageManager;
@Inject
- public StorageNotification(Context context) {
+ public StorageNotification(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ NotificationManager notificationManager,
+ StorageManager storageManager
+ ) {
mContext = context;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mNotificationManager = notificationManager;
+ mStorageManager = storageManager;
}
private static class MoveInfo {
@@ -168,17 +178,22 @@
@Override
public void start() {
- mNotificationManager = mContext.getSystemService(NotificationManager.class);
-
- mStorageManager = mContext.getSystemService(StorageManager.class);
mStorageManager.registerListener(mListener);
- mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
- android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
- mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
- android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ mBroadcastDispatcher.registerReceiver(
+ mSnoozeReceiver,
+ new IntentFilter(ACTION_SNOOZE_VOLUME),
+ null,
+ null,
+ Context.RECEIVER_EXPORTED_UNAUDITED,
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ mBroadcastDispatcher.registerReceiver(
+ mFinishReceiver,
+ new IntentFilter(ACTION_FINISH_WIZARD),
+ null,
+ null,
+ Context.RECEIVER_EXPORTED_UNAUDITED,
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
// Kick current state into place
final List<DiskInfo> disks = mStorageManager.getDisks();
@@ -381,7 +396,7 @@
// Don't annoy when user dismissed in past. (But make sure the disk is adoptable; we
// used to allow snoozing non-adoptable disks too.)
- if (rec.isSnoozed() && disk.isAdoptable()) {
+ if (rec == null || (rec.isSnoozed() && disk.isAdoptable())) {
return null;
}
if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index ffaf524..4c9b8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -19,31 +19,19 @@
import android.content.Context
import android.content.pm.UserInfo
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.util.UserIcons
import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
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.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.util.concurrent.atomic.AtomicBoolean
@@ -55,7 +43,6 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -72,15 +59,6 @@
* upstream changes.
*/
interface UserRepository {
- /** List of all users on the device. */
- val users: Flow<List<UserModel>>
-
- /** The currently-selected user. */
- val selectedUser: Flow<UserModel>
-
- /** List of available user-related actions. */
- val actions: Flow<List<UserActionModel>>
-
/** User switcher related settings. */
val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
@@ -93,9 +71,6 @@
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
- /** Whether actions are available even when locked. */
- val isActionableWhenLocked: Flow<Boolean>
-
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean
@@ -105,6 +80,9 @@
/** Whether we've scheduled the creation of a guest user. */
val isGuestUserCreationScheduled: AtomicBoolean
+ /** Whether to enable the status bar user chip (which launches the user switcher) */
+ val isStatusBarUserChipEnabled: Boolean
+
/** The user of the secondary service. */
var secondaryUserId: Int
@@ -125,18 +103,13 @@
constructor(
@Application private val appContext: Context,
private val manager: UserManager,
- private val controller: UserSwitcherController,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
- private val featureFlags: FeatureFlags,
) : UserRepository {
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow().filterNotNull()
@@ -150,70 +123,24 @@
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
private set
- private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
- fun send() {
- trySendWithFailureLogging(
- controller.users,
- TAG,
- )
- }
-
- val callback = UserSwitcherController.UserSwitchCallback { send() }
-
- controller.addUserSwitchCallback(callback)
- send()
-
- awaitClose { controller.removeUserSwitchCallback(callback) }
- }
-
- override val users: Flow<List<UserModel>> =
- userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
-
- override val selectedUser: Flow<UserModel> =
- users.map { users -> users.first { user -> user.isSelected } }
-
- override val actions: Flow<List<UserActionModel>> =
- userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
-
- override val isActionableWhenLocked: Flow<Boolean> =
- if (isNewImpl) {
- emptyFlow()
- } else {
- controller.isAddUsersFromLockScreenEnabled
- }
-
override val isGuestUserAutoCreated: Boolean =
- if (isNewImpl) {
- appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
- } else {
- controller.isGuestUserAutoCreated
- }
+ appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
private var _isGuestUserResetting: Boolean = false
- override var isGuestUserResetting: Boolean =
- if (isNewImpl) {
- _isGuestUserResetting
- } else {
- controller.isGuestUserResetting
- }
- set(value) =
- if (isNewImpl) {
- _isGuestUserResetting = value
- } else {
- error("Not supported in the old implementation!")
- }
+ override var isGuestUserResetting: Boolean = _isGuestUserResetting
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override val isStatusBarUserChipEnabled: Boolean =
+ appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
init {
- if (isNewImpl) {
- observeSelectedUser()
- observeUserSettings()
- }
+ observeSelectedUser()
+ observeUserSettings()
}
override fun refreshUsers() {
@@ -327,64 +254,6 @@
}
}
- private fun UserRecord.isUser(): Boolean {
- return when {
- isAddUser -> false
- isAddSupervisedUser -> false
- isManageUsers -> false
- isGuest -> info != null
- else -> true
- }
- }
-
- private fun UserRecord.isNotUser(): Boolean {
- return !isUser()
- }
-
- private fun UserRecord.toUserModel(): UserModel {
- return UserModel(
- id = resolveId(),
- name = getUserName(this),
- image = getUserImage(this),
- isSelected = isCurrent,
- isSelectable = isSwitchToEnabled || isGuest,
- isGuest = isGuest,
- )
- }
-
- private fun UserRecord.toActionModel(): UserActionModel {
- return when {
- isAddUser -> UserActionModel.ADD_USER
- isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
- isGuest -> UserActionModel.ENTER_GUEST_MODE
- isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
- else -> error("Don't know how to convert to UserActionModel: $this")
- }
- }
-
- private fun getUserName(record: UserRecord): Text {
- val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
- return if (resourceId != null) {
- Text.Resource(resourceId)
- } else {
- Text.Loaded(checkNotNull(record.info).name)
- }
- }
-
- private fun getUserImage(record: UserRecord): Drawable {
- if (record.isGuest) {
- return checkNotNull(
- AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
- )
- }
-
- val userId = checkNotNull(record.info?.id)
- return manager.getUserIcon(userId)?.let { userSelectedIcon ->
- BitmapDrawable(userSelectedIcon)
- }
- ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
- }
-
companion object {
private const val TAG = "UserRepository"
@VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index dda78aa..6672469 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -34,6 +34,7 @@
import com.android.internal.util.UserIcons
import com.android.systemui.R
import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
@@ -44,8 +45,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -64,8 +66,6 @@
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.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -82,7 +82,6 @@
constructor(
@Application private val applicationContext: Context,
private val repository: UserRepository,
- private val controller: UserSwitcherController,
private val activityStarter: ActivityStarter,
private val keyguardInteractor: KeyguardInteractor,
private val featureFlags: FeatureFlags,
@@ -107,9 +106,6 @@
fun onUserStateChanged()
}
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
private val supervisedUserPackageName: String?
get() =
applicationContext.getString(
@@ -118,185 +114,139 @@
private val callbackMutex = Mutex()
private val callbacks = mutableSetOf<UserCallback>()
+ private val userInfos =
+ combine(repository.userSwitcherSettings, repository.userInfos) { settings, userInfos ->
+ userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }.filter { it.isFull }
+ }
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
get() =
- if (isNewImpl) {
- combine(
- repository.userInfos,
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, settings ->
- toUserModels(
- userInfos = userInfos,
- selectedUserId = selectedUserInfo.id,
- isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
- )
- }
- } else {
- repository.users
+ combine(
+ userInfos,
+ repository.selectedUserInfo,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, settings ->
+ toUserModels(
+ userInfos = userInfos,
+ selectedUserId = selectedUserInfo.id,
+ isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+ )
}
/** The currently-selected user. */
val selectedUser: Flow<UserModel>
get() =
- if (isNewImpl) {
- combine(
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { selectedUserInfo, settings ->
- val selectedUserId = selectedUserInfo.id
- checkNotNull(
- toUserModel(
- userInfo = selectedUserInfo,
- selectedUserId = selectedUserId,
- canSwitchUsers = canSwitchUsers(selectedUserId),
- isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
- )
- )
- }
- } else {
- repository.selectedUser
+ repository.selectedUserInfo.map { selectedUserInfo ->
+ val selectedUserId = selectedUserInfo.id
+ toUserModel(
+ userInfo = selectedUserInfo,
+ selectedUserId = selectedUserId,
+ canSwitchUsers = canSwitchUsers(selectedUserId)
+ )
}
/** List of user-switcher related actions that are available. */
val actions: Flow<List<UserActionModel>>
get() =
- if (isNewImpl) {
- combine(
- repository.selectedUserInfo,
- repository.userInfos,
- repository.userSwitcherSettings,
- keyguardInteractor.isKeyguardShowing,
- ) { _, userInfos, settings, isDeviceLocked ->
- buildList {
- val hasGuestUser = userInfos.any { it.isGuest }
- if (
- !hasGuestUser &&
- (guestUserInteractor.isGuestUserAutoCreated ||
- UserActionsUtil.canCreateGuest(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- ))
- ) {
- add(UserActionModel.ENTER_GUEST_MODE)
- }
+ combine(
+ repository.selectedUserInfo,
+ userInfos,
+ repository.userSwitcherSettings,
+ keyguardInteractor.isKeyguardShowing,
+ ) { _, userInfos, settings, isDeviceLocked ->
+ buildList {
+ val hasGuestUser = userInfos.any { it.isGuest }
+ if (!hasGuestUser && canCreateGuestUser(settings)) {
+ add(UserActionModel.ENTER_GUEST_MODE)
+ }
- if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
- // The device is locked and our setting to allow actions that add users
- // from the lock-screen is not enabled. The guest action from above is
- // always allowed, even when the device is locked, but the various "add
- // user" actions below are not. We can finish building the list here.
+ if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+ // The device is locked and our setting to allow actions that add users
+ // from the lock-screen is not enabled. The guest action from above is
+ // always allowed, even when the device is locked, but the various "add
+ // user" actions below are not. We can finish building the list here.
- val canCreateUsers =
- UserActionsUtil.canCreateUser(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- )
-
- if (canCreateUsers) {
- add(UserActionModel.ADD_USER)
- }
-
- if (
- UserActionsUtil.canCreateSupervisedUser(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- supervisedUserPackageName,
- )
- ) {
- add(UserActionModel.ADD_SUPERVISED_USER)
- }
- }
-
- if (
- UserActionsUtil.canManageUsers(
+ val canCreateUsers =
+ UserActionsUtil.canCreateUser(
+ manager,
repository,
settings.isUserSwitcherEnabled,
settings.isAddUsersFromLockscreen,
)
- ) {
- add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+ if (canCreateUsers) {
+ add(UserActionModel.ADD_USER)
}
+
+ if (
+ UserActionsUtil.canCreateSupervisedUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ supervisedUserPackageName,
+ )
+ ) {
+ add(UserActionModel.ADD_SUPERVISED_USER)
+ }
+ }
+
+ if (
+ UserActionsUtil.canManageUsers(
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ ) {
+ add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
}
}
- } else {
- combine(
- repository.isActionableWhenLocked,
- keyguardInteractor.isKeyguardShowing,
- ) { isActionableWhenLocked, isLocked ->
- isActionableWhenLocked || !isLocked
- }
- .flatMapLatest { isActionable ->
- if (isActionable) {
- repository.actions
- } else {
- // If not actionable it means that we're not allowed to show actions
- // when
- // locked and we are locked. Therefore, we should show no actions.
- flowOf(emptyList())
- }
- }
}
val userRecords: StateFlow<ArrayList<UserRecord>> =
- if (isNewImpl) {
- combine(
- repository.userInfos,
- repository.selectedUserInfo,
- actions,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, actionModels, settings ->
- ArrayList(
- userInfos.map {
+ combine(
+ userInfos,
+ repository.selectedUserInfo,
+ actions,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, actionModels, settings ->
+ ArrayList(
+ userInfos.map {
+ toRecord(
+ userInfo = it,
+ selectedUserId = selectedUserInfo.id,
+ )
+ } +
+ actionModels.map {
toRecord(
- userInfo = it,
+ action = it,
selectedUserId = selectedUserInfo.id,
+ isRestricted =
+ it != UserActionModel.ENTER_GUEST_MODE &&
+ it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+ !settings.isAddUsersFromLockscreen,
)
- } +
- actionModels.map {
- toRecord(
- action = it,
- selectedUserId = selectedUserInfo.id,
- isRestricted =
- it != UserActionModel.ENTER_GUEST_MODE &&
- it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
- !settings.isAddUsersFromLockscreen,
- )
- }
- )
- }
- .onEach { notifyCallbacks() }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = ArrayList(),
+ }
)
- } else {
- MutableStateFlow(ArrayList())
- }
+ }
+ .onEach { notifyCallbacks() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = ArrayList(),
+ )
val selectedUserRecord: StateFlow<UserRecord?> =
- if (isNewImpl) {
- repository.selectedUserInfo
- .map { selectedUserInfo ->
- toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
- } else {
- MutableStateFlow(null)
- }
+ repository.selectedUserInfo
+ .map { selectedUserInfo ->
+ toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
@@ -304,6 +254,9 @@
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+ /** Whether to enable the user chip in the status bar */
+ val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -311,44 +264,37 @@
val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
val isSimpleUserSwitcher: Boolean
- get() =
- if (isNewImpl) {
- repository.isSimpleUserSwitcher()
- } else {
- error("Not supported in the old implementation!")
- }
+ get() = repository.isSimpleUserSwitcher()
init {
- if (isNewImpl) {
- refreshUsersScheduler.refreshIfNotPaused()
- telephonyInteractor.callState
- .distinctUntilChanged()
- .onEach { refreshUsersScheduler.refreshIfNotPaused() }
- .launchIn(applicationScope)
+ refreshUsersScheduler.refreshIfNotPaused()
+ telephonyInteractor.callState
+ .distinctUntilChanged()
+ .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+ .launchIn(applicationScope)
- combine(
- broadcastDispatcher.broadcastFlow(
- filter =
- IntentFilter().apply {
- addAction(Intent.ACTION_USER_ADDED)
- addAction(Intent.ACTION_USER_REMOVED)
- addAction(Intent.ACTION_USER_INFO_CHANGED)
- addAction(Intent.ACTION_USER_SWITCHED)
- addAction(Intent.ACTION_USER_STOPPED)
- addAction(Intent.ACTION_USER_UNLOCKED)
- },
- user = UserHandle.SYSTEM,
- map = { intent, _ -> intent },
- ),
- repository.selectedUserInfo.pairwise(null),
- ) { intent, selectedUserChange ->
- Pair(intent, selectedUserChange.previousValue)
- }
- .onEach { (intent, previousSelectedUser) ->
- onBroadcastReceived(intent, previousSelectedUser)
- }
- .launchIn(applicationScope)
- }
+ combine(
+ broadcastDispatcher.broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_USER_ADDED)
+ addAction(Intent.ACTION_USER_REMOVED)
+ addAction(Intent.ACTION_USER_INFO_CHANGED)
+ addAction(Intent.ACTION_USER_SWITCHED)
+ addAction(Intent.ACTION_USER_STOPPED)
+ addAction(Intent.ACTION_USER_UNLOCKED)
+ },
+ user = UserHandle.SYSTEM,
+ map = { intent, _ -> intent },
+ ),
+ repository.selectedUserInfo.pairwise(null),
+ ) { intent, selectedUserChange ->
+ Pair(intent, selectedUserChange.previousValue)
+ }
+ .onEach { (intent, previousSelectedUser) ->
+ onBroadcastReceived(intent, previousSelectedUser)
+ }
+ .launchIn(applicationScope)
}
fun addCallback(callback: UserCallback) {
@@ -414,48 +360,43 @@
newlySelectedUserId: Int,
dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
- if (isNewImpl) {
- val currentlySelectedUserInfo = repository.getSelectedUserInfo()
- if (
- newlySelectedUserId == currentlySelectedUserInfo.id &&
- currentlySelectedUserInfo.isGuest
- ) {
- // Here when clicking on the currently-selected guest user to leave guest mode
- // and return to the previously-selected non-guest user.
- showDialog(
- ShowDialogRequestModel.ShowExitGuestDialog(
- guestUserId = currentlySelectedUserInfo.id,
- targetUserId = repository.lastSelectedNonGuestUserId,
- isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- onExitGuestUser = this::exitGuestUser,
- dialogShower = dialogShower,
- )
+ val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+ if (
+ newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest
+ ) {
+ // Here when clicking on the currently-selected guest user to leave guest mode
+ // and return to the previously-selected non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = repository.lastSelectedNonGuestUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
)
- return
- }
-
- if (currentlySelectedUserInfo.isGuest) {
- // Here when switching from guest to a non-guest user.
- showDialog(
- ShowDialogRequestModel.ShowExitGuestDialog(
- guestUserId = currentlySelectedUserInfo.id,
- targetUserId = newlySelectedUserId,
- isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- onExitGuestUser = this::exitGuestUser,
- dialogShower = dialogShower,
- )
- )
- return
- }
-
- dialogShower?.dismiss()
-
- switchUser(newlySelectedUserId)
- } else {
- controller.onUserSelected(newlySelectedUserId, dialogShower)
+ )
+ return
}
+
+ if (currentlySelectedUserInfo.isGuest) {
+ // Here when switching from guest to a non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = newlySelectedUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
+ )
+ )
+ return
+ }
+
+ dialogShower?.dismiss()
+
+ switchUser(newlySelectedUserId)
}
/** Executes the given action. */
@@ -463,51 +404,38 @@
action: UserActionModel,
dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
- if (isNewImpl) {
- when (action) {
- UserActionModel.ENTER_GUEST_MODE ->
- guestUserInteractor.createAndSwitchTo(
- this::showDialog,
- this::dismissDialog,
- ) { userId ->
- selectUser(userId, dialogShower)
- }
- UserActionModel.ADD_USER -> {
- val currentUser = repository.getSelectedUserInfo()
- showDialog(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = currentUser.userHandle,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
- dialogShower = dialogShower,
- )
- )
+ when (action) {
+ UserActionModel.ENTER_GUEST_MODE ->
+ guestUserInteractor.createAndSwitchTo(
+ this::showDialog,
+ this::dismissDialog,
+ ) { userId ->
+ selectUser(userId, dialogShower)
}
- UserActionModel.ADD_SUPERVISED_USER ->
- activityStarter.startActivity(
- Intent()
- .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
- .setPackage(supervisedUserPackageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
- /* dismissShade= */ true,
+ UserActionModel.ADD_USER -> {
+ val currentUser = repository.getSelectedUserInfo()
+ showDialog(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = currentUser.userHandle,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+ dialogShower = dialogShower,
)
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
- activityStarter.startActivity(
- Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ true,
- )
+ )
}
- } else {
- when (action) {
- UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
- UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
- UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
- activityStarter.startActivity(
- Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ false,
- )
- }
+ UserActionModel.ADD_SUPERVISED_USER ->
+ activityStarter.startActivity(
+ Intent()
+ .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ .setPackage(supervisedUserPackageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ /* dismissShade= */ true,
+ )
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_USER_SETTINGS),
+ /* dismissShade= */ true,
+ )
}
}
@@ -541,6 +469,26 @@
}
}
+ fun showUserSwitcher(context: Context, expandable: Expandable) {
+ if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ return
+ }
+
+ val intent =
+ Intent(context, UserSwitcherActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ expandable.activityLaunchController(),
+ true /* showOverlockscreenwhenlocked */,
+ UserHandle.SYSTEM,
+ )
+ }
+
private fun showDialog(request: ShowDialogRequestModel) {
_dialogShowRequests.value = request
}
@@ -674,7 +622,7 @@
// The guest user should go in the last position.
.sortedBy { it.isGuest }
.mapNotNull { userInfo ->
- toUserModel(
+ filterAndMapToUserModel(
userInfo = userInfo,
selectedUserId = selectedUserId,
canSwitchUsers = canSwitchUsers,
@@ -683,51 +631,65 @@
}
}
- private suspend fun toUserModel(
+ /**
+ * Maps UserInfo to UserModel based on some parameters and return null under certain conditions
+ * to be filtered out.
+ */
+ private suspend fun filterAndMapToUserModel(
userInfo: UserInfo,
selectedUserId: Int,
canSwitchUsers: Boolean,
isUserSwitcherEnabled: Boolean,
): UserModel? {
- val userId = userInfo.id
- val isSelected = userId == selectedUserId
-
return when {
// When the user switcher is not enabled in settings, we only show the primary user.
!isUserSwitcherEnabled && !userInfo.isPrimary -> null
-
// We avoid showing disabled users.
!userInfo.isEnabled -> null
- userInfo.isGuest ->
- UserModel(
- id = userId,
- name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = true,
- userId = userId,
- ),
- isSelected = isSelected,
- isSelectable = canSwitchUsers,
- isGuest = true,
- )
- userInfo.supportsSwitchToByUser() ->
- UserModel(
- id = userId,
- name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = false,
- userId = userId,
- ),
- isSelected = isSelected,
- isSelectable = canSwitchUsers || isSelected,
- isGuest = false,
- )
+ // We meet the conditions to return the UserModel.
+ userInfo.isGuest || userInfo.supportsSwitchToByUser() ->
+ toUserModel(userInfo, selectedUserId, canSwitchUsers)
else -> null
}
}
+ /** Maps UserInfo to UserModel based on some parameters. */
+ private suspend fun toUserModel(
+ userInfo: UserInfo,
+ selectedUserId: Int,
+ canSwitchUsers: Boolean
+ ): UserModel {
+ val userId = userInfo.id
+ val isSelected = userId == selectedUserId
+ return if (userInfo.isGuest) {
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = true,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers,
+ isGuest = true,
+ )
+ } else {
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = false,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers || isSelected,
+ isGuest = false,
+ )
+ }
+ }
+
private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
return withContext(backgroundDispatcher) {
manager.getUserSwitchability(UserHandle.of(selectedUserId))
@@ -757,6 +719,16 @@
)
}
+ private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+ return guestUserInteractor.isGuestUserAutoCreated ||
+ UserActionsUtil.canCreateGuest(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ }
+
companion object {
private const val TAG = "UserInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 177356e..85c2964 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -43,4 +43,7 @@
val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
override val dialogShower: UserSwitchDialogController.DialogShower?,
) : ShowDialogRequestModel(dialogShower)
+
+ /** Show the user switcher dialog */
+ object ShowUserSwitcherDialog : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
new file mode 100644
index 0000000..8e40f68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.user.ui.binder
+
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@OptIn(InternalCoroutinesApi::class)
+object StatusBarUserChipViewBinder {
+ /** Binds the status bar user chip view model to the given view */
+ @JvmStatic
+ fun bind(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isChipVisible.collect { isVisible -> view.isVisible = isVisible }
+ }
+
+ launch {
+ viewModel.userName.collect { name -> TextViewBinder.bind(view.text, name) }
+ }
+
+ launch {
+ viewModel.userAvatar.collect { avatar -> view.avatar.setImageDrawable(avatar) }
+ }
+
+ bindButton(view, viewModel)
+ }
+ }
+ }
+
+ private fun bindButton(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.setOnClickListener { viewModel.onClick(Expandable.fromView(view)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index ad09ee3..e137107 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -133,7 +133,9 @@
launch {
viewModel.users.collect { users ->
val viewPool =
- view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
+ gridContainerView.children
+ .filter { it.tag == USER_VIEW_TAG }
+ .toMutableList()
viewPool.forEach {
gridContainerView.removeView(it)
flowWidget.removeView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
new file mode 100644
index 0000000..ed25898
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -0,0 +1,68 @@
+package com.android.systemui.user.ui.dialog
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.LayoutInflater
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/**
+ * Extracted from the old UserSwitchDialogController. This is the dialog version of the full-screen
+ * user switcher. See config_enableFullscreenUserSwitcher
+ */
+class UserSwitchDialog(
+ context: Context,
+ adapter: UserDetailView.Adapter,
+ uiEventLogger: UiEventLogger,
+ falsingManager: FalsingManager,
+ activityStarter: ActivityStarter,
+ dialogLaunchAnimator: DialogLaunchAnimator,
+) : SystemUIDialog(context) {
+ init {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ setTitle(R.string.qs_user_switch_dialog_title)
+ setPositiveButton(R.string.quick_settings_done) { _, _ ->
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
+ }
+ setNeutralButton(
+ R.string.quick_settings_more_user_settings,
+ { _, _ ->
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
+ val controller =
+ dialogLaunchAnimator.createActivityLaunchController(
+ getButton(BUTTON_NEUTRAL)
+ )
+
+ if (controller == null) {
+ dismiss()
+ }
+
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0,
+ controller
+ )
+ }
+ },
+ false /* dismissOnClick */
+ )
+ val gridFrame =
+ LayoutInflater.from(this.context).inflate(R.layout.qs_user_dialog_content, null)
+ setView(gridFrame)
+
+ adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+ }
+
+ companion object {
+ private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index e921720..4141054 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -20,6 +20,7 @@
import android.app.Dialog
import android.content.Context
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
@@ -27,15 +28,15 @@
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import dagger.Lazy
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -50,16 +51,14 @@
private val broadcastSender: Lazy<BroadcastSender>,
private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
private val interactor: Lazy<UserInteractor>,
- private val featureFlags: Lazy<FeatureFlags>,
+ private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
+ private val eventLogger: Lazy<UiEventLogger>,
+ private val activityStarter: Lazy<ActivityStarter>,
) : CoreStartable {
private var currentDialog: Dialog? = null
override fun start() {
- if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
- return
- }
-
startHandlingDialogShowRequests()
startHandlingDialogDismissRequests()
}
@@ -116,6 +115,21 @@
INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
),
)
+ is ShowDialogRequestModel.ShowUserSwitcherDialog ->
+ Pair(
+ UserSwitchDialog(
+ context = context.get(),
+ adapter = userDetailAdapterProvider.get(),
+ uiEventLogger = eventLogger.get(),
+ falsingManager = falsingManager.get(),
+ activityStarter = activityStarter.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+ ),
+ )
}
currentDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
new file mode 100644
index 0000000..3300e8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.user.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarUserChipViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ interactor: UserInteractor,
+) {
+ /** Whether the status bar chip ui should be available */
+ val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+
+ /** Whether or not the chip should be showing, based on the number of users */
+ val isChipVisible: Flow<Boolean> =
+ if (!chipEnabled) {
+ flowOf(false)
+ } else {
+ interactor.users.mapLatest { users -> users.size > 1 }
+ }
+
+ /** The display name of the current user */
+ val userName: Flow<Text> = interactor.selectedUser.mapLatest { userModel -> userModel.name }
+
+ /** Avatar for the current user */
+ val userAvatar: Flow<Drawable> =
+ interactor.selectedUser.mapLatest { userModel -> userModel.image }
+
+ /** Action to execute on click. Should launch the user switcher */
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index d857e85..0910ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,8 +20,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
@@ -41,12 +39,8 @@
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
- private val featureFlags: FeatureFlags,
) : ViewModel() {
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
/** On-device users. */
val users: Flow<List<UserViewModel>> =
userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -216,7 +210,6 @@
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
- private val featureFlags: FeatureFlags,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
@@ -224,7 +217,6 @@
userInteractor = userInteractor,
guestUserInteractor = guestUserInteractor,
powerInteractor = powerInteractor,
- featureFlags = featureFlags,
)
as T
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt
new file mode 100644
index 0000000..12a0c03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.content.pm.ActivityInfo
+import android.content.res.Resources
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+import android.graphics.drawable.InsetDrawable
+
+/**
+ * [DrawableWrapper] to use in the progress of brightness slider.
+ *
+ * This drawable is used to change the bounds of the enclosed drawable depending on the level to
+ * simulate a sliding progress, instead of using clipping or scaling. That way, the shape of the
+ * edges is maintained.
+ *
+ * Meant to be used with a rounded ends background, it will also prevent deformation when the slider
+ * is meant to be smaller than the rounded corner. The background should have rounded corners that
+ * are half of the height.
+ *
+ * This class also assumes that a "thumb" icon exists within the end's edge of the progress
+ * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb
+ * icon which puts the icon directly underneath the user's finger.
+ */
+class BrightnessProgressDrawable @JvmOverloads constructor(drawable: Drawable? = null) :
+ InsetDrawable(drawable, 0) {
+
+ companion object {
+ private const val MAX_LEVEL = 10000 // Taken from Drawable
+ }
+
+ override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+ onLevelChange(level)
+ return super.onLayoutDirectionChanged(layoutDirection)
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ onLevelChange(level)
+ }
+
+ override fun onLevelChange(level: Int): Boolean {
+ val db = drawable?.bounds!!
+
+ // The thumb offset shifts the sun icon directly under the user's thumb
+ val thumbOffset = bounds.height() / 2
+ val width = bounds.width() * level / MAX_LEVEL + thumbOffset
+
+ // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width
+ drawable?.setBounds(
+ bounds.left,
+ db.top,
+ width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()),
+ db.bottom
+ )
+ return super.onLevelChange(level)
+ }
+
+ override fun getConstantState(): ConstantState {
+ // This should not be null as it was created with a state in the constructor.
+ return RoundedCornerState(super.getConstantState()!!)
+ }
+
+ override fun getChangingConfigurations(): Int {
+ return super.getChangingConfigurations() or ActivityInfo.CONFIG_DENSITY
+ }
+
+ override fun canApplyTheme(): Boolean {
+ return (drawable?.canApplyTheme() ?: false) || super.canApplyTheme()
+ }
+
+ private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() {
+ override fun newDrawable(): Drawable {
+ return newDrawable(null, null)
+ }
+
+ override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable {
+ val wrapper = wrappedState.newDrawable(res, theme) as DrawableWrapper
+ return BrightnessProgressDrawable(wrapper.drawable)
+ }
+
+ override fun getChangingConfigurations(): Int {
+ return wrappedState.changingConfigurations
+ }
+
+ override fun canApplyTheme(): Boolean {
+ return true
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 6b5556b..0f3eddf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -19,7 +19,6 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -53,8 +52,8 @@
/**
* Wrapped version of {@link DeviceConfig#enforceReadPermission}.
*/
- public void enforceReadPermission(Context context, String namespace) {
- DeviceConfig.enforceReadPermission(context, namespace);
+ public void enforceReadPermission(String namespace) {
+ DeviceConfig.enforceReadPermission(namespace);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
index 99eb03b..1059d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -33,11 +33,6 @@
* Meant to be used with a rounded ends background, it will also prevent deformation when the slider
* is meant to be smaller than the rounded corner. The background should have rounded corners that
* are half of the height.
- *
- * This class also assumes that a "thumb" icon exists within the end's edge of the progress
- * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb
- * icon which puts the icon directly underneath the user's finger.
- *
*/
class RoundedCornerProgressDrawable @JvmOverloads constructor(
drawable: Drawable? = null
@@ -59,16 +54,9 @@
override fun onLevelChange(level: Int): Boolean {
val db = drawable?.bounds!!
-
- // The thumb offset shifts the sun icon directly under the user's thumb
- val thumbOffset = bounds.height() / 2
- val width = bounds.width() * level / MAX_LEVEL + thumbOffset
-
// On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width
- drawable?.setBounds(
- bounds.left, db.top,
- width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()), db.bottom
- )
+ val width = bounds.height() + (bounds.width() - bounds.height()) * level / MAX_LEVEL
+ drawable?.setBounds(bounds.left, db.top, bounds.left + width, db.bottom)
return super.onLevelChange(level)
}
@@ -103,4 +91,4 @@
return true
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
new file mode 100644
index 0000000..da81d54
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.util.condition
+
+/**
+ * A higher order [Condition] which combines multiple conditions with a specified
+ * [Evaluator.ConditionOperand].
+ */
+internal class CombinedCondition
+constructor(
+ private val conditions: Collection<Condition>,
+ @Evaluator.ConditionOperand private val operand: Int
+) : Condition(null, false), Condition.Callback {
+
+ override fun start() {
+ onConditionChanged(this)
+ conditions.forEach { it.addCallback(this) }
+ }
+
+ override fun onConditionChanged(condition: Condition) {
+ Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) }
+ ?: clearCondition()
+ }
+
+ override fun stop() {
+ conditions.forEach { it.removeCallback(this) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index 2c317dd..b39adef 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -24,7 +24,10 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
/**
* Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
@@ -181,6 +184,42 @@
}
/**
+ * Creates a new condition which will only be true when both this condition and all the provided
+ * conditions are true.
+ */
+ public Condition and(Collection<Condition> others) {
+ final List<Condition> conditions = new ArrayList<>(others);
+ conditions.add(this);
+ return new CombinedCondition(conditions, Evaluator.OP_AND);
+ }
+
+ /**
+ * Creates a new condition which will only be true when both this condition and the provided
+ * condition is true.
+ */
+ public Condition and(Condition other) {
+ return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
+ }
+
+ /**
+ * Creates a new condition which will only be true when either this condition or any of the
+ * provided conditions are true.
+ */
+ public Condition or(Collection<Condition> others) {
+ final List<Condition> conditions = new ArrayList<>(others);
+ conditions.add(this);
+ return new CombinedCondition(conditions, Evaluator.OP_OR);
+ }
+
+ /**
+ * Creates a new condition which will only be true when either this condition or the provided
+ * condition is true.
+ */
+ public Condition or(Condition other) {
+ return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
+ }
+
+ /**
* Callback that receives updates about whether the condition has been fulfilled.
*/
public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
new file mode 100644
index 0000000..cf44e84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.util.condition
+
+import android.annotation.IntDef
+
+/**
+ * Helper for evaluating a collection of [Condition] objects with a given
+ * [Evaluator.ConditionOperand]
+ */
+internal object Evaluator {
+ /** Operands for combining multiple conditions together */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(value = [OP_AND, OP_OR])
+ annotation class ConditionOperand
+
+ /**
+ * 3-valued logical AND operand, with handling for unknown values (represented as null)
+ *
+ * ```
+ * +-----+----+---+---+
+ * | AND | T | F | U |
+ * +-----+----+---+---+
+ * | T | T | F | U |
+ * | F | F | F | F |
+ * | U | U | F | U |
+ * +-----+----+---+---+
+ * ```
+ */
+ const val OP_AND = 0
+
+ /**
+ * 3-valued logical OR operand, with handling for unknown values (represented as null)
+ *
+ * ```
+ * +-----+----+---+---+
+ * | OR | T | F | U |
+ * +-----+----+---+---+
+ * | T | T | T | T |
+ * | F | T | F | U |
+ * | U | T | U | U |
+ * +-----+----+---+---+
+ * ```
+ */
+ const val OP_OR = 1
+
+ /**
+ * Evaluates a set of conditions with a given operand
+ *
+ * If overriding conditions are present, they take precedence over normal conditions if set.
+ *
+ * @param conditions The collection of conditions to evaluate. If empty, null is returned.
+ * @param operand The operand to use when evaluating.
+ * @return Either true or false if the value is known, or null if value is unknown
+ */
+ fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? {
+ if (conditions.isEmpty()) return null
+ // If there are overriding conditions with values set, they take precedence.
+ val targetConditions =
+ conditions
+ .filter { it.isConditionSet && it.isOverridingCondition }
+ .ifEmpty { conditions }
+ return when (operand) {
+ OP_AND ->
+ threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false)
+ OP_OR ->
+ threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true)
+ else -> null
+ }
+ }
+
+ /**
+ * Helper for evaluating 3-valued logical AND/OR.
+ *
+ * @param returnValueIfAnyMatches AND returns false if any value is false. OR returns true if
+ * any value is true.
+ */
+ private fun threeValuedAndOrOr(
+ conditions: Collection<Condition>,
+ returnValueIfAnyMatches: Boolean
+ ): Boolean? {
+ var hasUnknown = false
+ for (condition in conditions) {
+ if (!condition.isConditionSet) {
+ hasUnknown = true
+ continue
+ }
+ if (condition.isConditionMet == returnValueIfAnyMatches) {
+ return returnValueIfAnyMatches
+ }
+ }
+ return if (hasUnknown) null else !returnValueIfAnyMatches
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index cb430ba..24bc907 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -24,12 +24,10 @@
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -57,21 +55,10 @@
}
public void update() {
- // Only consider set conditions.
- final Collection<Condition> setConditions = mSubscription.mConditions.stream()
- .filter(Condition::isConditionSet).collect(Collectors.toSet());
-
- // Overriding conditions do not override each other
- final Collection<Condition> overridingConditions = setConditions.stream()
- .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
-
- final Collection<Condition> targetCollection = overridingConditions.isEmpty()
- ? setConditions : overridingConditions;
-
- final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
- .stream()
- .map(Condition::isConditionMet)
- .allMatch(conditionMet -> conditionMet);
+ final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
+ Evaluator.OP_AND);
+ // Consider unknown (null) as true
+ final boolean newAllConditionsMet = result == null || result;
if (mAllConditionsMet != null && newAllConditionsMet == mAllConditionsMet) {
return;
@@ -109,6 +96,7 @@
/**
* Registers a callback and the set of conditions to trigger it.
+ *
* @param subscription A {@link Subscription} detailing the desired conditions and callback.
* @return A {@link Subscription.Token} that can be used to remove the subscription.
*/
@@ -139,6 +127,7 @@
/**
* Removes a subscription from participating in future callbacks.
+ *
* @param token The {@link Subscription.Token} returned when the {@link Subscription} was
* originally added.
*/
@@ -179,7 +168,9 @@
private final Set<Condition> mConditions;
private final Callback mCallback;
- /** */
+ /**
+ *
+ */
public Subscription(Set<Condition> conditions, Callback callback) {
this.mConditions = Collections.unmodifiableSet(conditions);
this.mCallback = callback;
@@ -209,7 +200,6 @@
/**
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
- * @param callback
*/
public Builder(Callback callback) {
mCallback = callback;
@@ -218,7 +208,7 @@
/**
* Adds a {@link Condition} to be associated with the {@link Subscription}.
- * @param condition
+ *
* @return The updated {@link Builder}.
*/
public Builder addCondition(Condition condition) {
@@ -228,7 +218,7 @@
/**
* Adds a set of {@link Condition} to be associated with the {@link Subscription}.
- * @param condition
+ *
* @return The updated {@link Builder}.
*/
public Builder addConditions(Set<Condition> condition) {
@@ -238,6 +228,7 @@
/**
* Builds the {@link Subscription}.
+ *
* @return The resulting {@link Subscription}.
*/
public Subscription build() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 4875982..9b06a37 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -31,8 +31,8 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.concurrency.ThreadFactory;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
index d7c4e93..3c57081 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
@@ -16,10 +16,11 @@
package com.android.systemui.util.time;
-import android.app.ActivityManager;
import android.content.Context;
import android.text.format.DateFormat;
+import com.android.systemui.settings.UserTracker;
+
import javax.inject.Inject;
/**
@@ -27,14 +28,16 @@
*/
public class DateFormatUtil {
private final Context mContext;
+ private final UserTracker mUserTracker;
@Inject
- public DateFormatUtil(Context context) {
+ public DateFormatUtil(Context context, UserTracker userTracker) {
mContext = context;
+ mUserTracker = userTracker;
}
/** Returns true if the phone is in 24 hour format. */
public boolean is24HourFormat() {
- return DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+ return DateFormat.is24HourFormat(mContext, mUserTracker.getUserId());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 903aba1..d062fff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -72,6 +72,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.InputFilter;
@@ -108,6 +109,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.view.RotationPolicy;
@@ -125,11 +128,15 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.AlphaTintDrawableWrapper;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.RoundedCornerProgressDrawable;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -186,6 +193,9 @@
private ViewGroup mDialogRowsView;
private ViewGroup mRinger;
+ private DeviceConfigProxy mDeviceConfigProxy;
+ private Executor mExecutor;
+
/**
* Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
* volume rows, and the ellipsis button. This does not include the live caption button.
@@ -274,6 +284,13 @@
private BackgroundBlurDrawable mDialogRowsViewBackground;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private boolean mSeparateNotification;
+
+ @VisibleForTesting
+ int mVolumeRingerIconDrawableId;
+ @VisibleForTesting
+ int mVolumeRingerMuteIconDrawableId;
+
public VolumeDialogImpl(
Context context,
VolumeDialogController volumeDialogController,
@@ -283,7 +300,9 @@
MediaOutputDialogFactory mediaOutputDialogFactory,
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DeviceConfigProxy deviceConfigProxy,
+ Executor executor) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mController = volumeDialogController;
@@ -323,6 +342,50 @@
}
initDimens();
+
+ mDeviceConfigProxy = deviceConfigProxy;
+ mExecutor = executor;
+ mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+ updateRingerModeIconSet();
+ }
+
+ /**
+ * If ringer and notification are the same stream (T and earlier), use notification-like bell
+ * icon set.
+ * If ringer and notification are separated, then use generic speaker icons.
+ */
+ private void updateRingerModeIconSet() {
+ if (mSeparateNotification) {
+ mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
+ mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
+ } else {
+ mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
+ mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
+ }
+
+ if (mRingerDrawerMuteIcon != null) {
+ mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ }
+ if (mRingerDrawerNormalIcon != null) {
+ mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+ }
+ }
+
+ /**
+ * Change icon for ring stream (not ringer mode icon)
+ */
+ private void updateRingRowIcon() {
+ Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
+ .findFirst();
+ if (volumeRow.isPresent()) {
+ VolumeRow volRow = volumeRow.get();
+ volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
+ : R.drawable.ic_volume_ringer;
+ volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
+ : R.drawable.ic_volume_ringer_mute;
+ volRow.setIcon(volRow.iconRes, mContext.getTheme());
+ }
}
@Override
@@ -339,6 +402,9 @@
mController.getState();
mConfigurationController.addCallback(this);
+
+ mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mExecutor, this::onDeviceConfigChange);
}
@Override
@@ -346,6 +412,24 @@
mController.removeCallback(mControllerCallbackH);
mHandler.removeCallbacksAndMessages(null);
mConfigurationController.removeCallback(this);
+ mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
+ }
+
+ /**
+ * Update ringer mode icon based on the config
+ */
+ private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changeSet = properties.getKeyset();
+ if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+ boolean newVal = properties.getBoolean(
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+ if (newVal != mSeparateNotification) {
+ mSeparateNotification = newVal;
+ updateRingerModeIconSet();
+ updateRingRowIcon();
+
+ }
+ }
}
@Override
@@ -554,6 +638,9 @@
mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
+ mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+
setupRingerDrawer();
mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
@@ -577,8 +664,14 @@
addRow(AudioManager.STREAM_MUSIC,
R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
if (!AudioSystem.isSingleVolume(mContext)) {
- addRow(AudioManager.STREAM_RING,
- R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
+ if (mSeparateNotification) {
+ addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
+ R.drawable.ic_ring_volume_off, true, false);
+ } else {
+ addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
+ R.drawable.ic_volume_ringer, true, false);
+ }
+
addRow(STREAM_ALARM,
R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
addRow(AudioManager.STREAM_VOICE_CALL,
@@ -1230,6 +1323,9 @@
effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
break;
case RINGER_MODE_VIBRATE:
+ // Feedback handled by onStateChange, for feedback both when user toggles
+ // directly in volume dialog, or drags slider to a value of 0 in settings.
+ break;
default:
effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
}
@@ -1531,8 +1627,8 @@
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
break;
case AudioManager.RINGER_MODE_SILENT:
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
mContext.getString(R.string.volume_ringer_hint_unmute));
@@ -1541,14 +1637,14 @@
default:
boolean muted = (mAutomute && ss.level == 0) || ss.muted;
if (!isZenMuted && muted) {
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_unmute));
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
} else {
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+ mRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
if (mController.hasVibrator()) {
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_vibrate));
@@ -1630,9 +1726,8 @@
&& mState.ringerModeInternal != -1
&& mState.ringerModeInternal != state.ringerModeInternal
&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
}
-
mState = state;
mDynamic.clear();
// add any new dynamic rows
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c5792b9..8f10fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -20,6 +20,7 @@
import android.media.AudioManager;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -27,11 +28,14 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
+import java.util.concurrent.Executor;
+
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -55,7 +59,9 @@
MediaOutputDialogFactory mediaOutputDialogFactory,
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DeviceConfigProxy deviceConfigProxy,
+ @Main Executor executor) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -65,7 +71,9 @@
mediaOutputDialogFactory,
volumePanelFactory,
activityStarter,
- interactionJankMonitor);
+ interactionJankMonitor,
+ deviceConfigProxy,
+ executor);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index e52225b..2d257b9 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -93,6 +93,21 @@
android:excludeFromRecents="true"
/>
+ <activity android:name="com.android.systemui.controls.management.ControlsEditingActivityTest$TestableControlsEditingActivity"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
+ <activity android:name="com.android.systemui.controls.management.ControlsFavoritingActivityTest$TestableControlsFavoritingActivity"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
+ <activity android:name="com.android.systemui.controls.management.ControlsProviderSelectorActivityTest$TestableControlsProviderSelectorActivity"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
<activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
android:exported="false" />
@@ -112,6 +127,12 @@
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true" />
+ <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
+ android:exported="false"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true" />
+
<provider
android:name="androidx.startup.InitializationProvider"
tools:replace="android:authorities"
@@ -125,6 +146,12 @@
tools:replace="android:authorities"
tools:node="remove" />
+ <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+ android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.android.systemui.test.fileprovider"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index 0a9c745..ffedb30 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
@@ -46,7 +46,7 @@
@Test
public void testShowsTextField() {
mKeyguardMessageArea.setVisibility(View.INVISIBLE);
- mKeyguardMessageArea.setMessage("oobleck");
+ mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true);
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
@@ -55,7 +55,7 @@
public void testHiddenWhenBouncerHidden() {
mKeyguardMessageArea.setIsVisible(false);
mKeyguardMessageArea.setVisibility(View.INVISIBLE);
- mKeyguardMessageArea.setMessage("oobleck");
+ mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true);
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
@@ -63,7 +63,7 @@
@Test
public void testClearsTextField() {
mKeyguardMessageArea.setVisibility(View.VISIBLE);
- mKeyguardMessageArea.setMessage("");
+ mKeyguardMessageArea.setMessage("", /* animate= */ true);
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("");
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
index 7b9b39f..ba46a87 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
@@ -49,30 +49,30 @@
@Test
fun testSetSameMessage() {
val underTestSpy = spy(underTest)
- underTestSpy.setMessage("abc")
- underTestSpy.setMessage("abc")
+ underTestSpy.setMessage("abc", animate = true)
+ underTestSpy.setMessage("abc", animate = true)
verify(underTestSpy, times(1)).text = "abc"
}
@Test
fun testSetDifferentMessage() {
- underTest.setMessage("abc")
- underTest.setMessage("def")
+ underTest.setMessage("abc", animate = true)
+ underTest.setMessage("def", animate = true)
assertThat(underTest.text).isEqualTo("def")
}
@Test
fun testSetNullMessage() {
- underTest.setMessage(null)
+ underTest.setMessage(null, animate = true)
assertThat(underTest.text).isEqualTo("")
}
@Test
fun testSetNullClearsPreviousMessage() {
- underTest.setMessage("something not null")
+ underTest.setMessage("something not null", animate = true)
assertThat(underTest.text).isEqualTo("something not null")
- underTest.setMessage(null)
+ underTest.setMessage(null, animate = true)
assertThat(underTest.text).isEqualTo("")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 52b6b38..e8f8e25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -155,7 +155,8 @@
verify(configurationController).addCallback(capture(captor))
captor.value.onDensityOrFontScaleChanged()
- verify(events).onFontSettingChanged()
+ verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
+ verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 131cf7d..8839662 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -84,10 +84,9 @@
userId = user,
listening = false,
authInterruptActive = false,
- becauseCannotSkipBouncer = false,
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
- faceAuthenticated = false,
+ faceAndFpNotAuthenticated = false,
faceDisabled = false,
faceLockedOut = false,
fpLockedOut = false,
@@ -101,4 +100,6 @@
secureCameraLaunched = false,
switchingUser = false,
udfpsBouncerShowing = false,
+ udfpsFingerDown = false,
+ userNotTrustedOrDetectionIsNeeded = false
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 5d2b0ca..0e837d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -16,8 +16,11 @@
package com.android.keyguard;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -82,7 +85,7 @@
@Test
public void testClearsTextField() {
mMessageAreaController.setMessage("");
- verify(mKeyguardMessageArea).setMessage("");
+ verify(mKeyguardMessageArea).setMessage("", /* animate= */ true);
}
@Test
@@ -90,4 +93,11 @@
mMessageAreaController.setIsVisible(true);
verify(mKeyguardMessageArea).setIsVisible(true);
}
+
+ @Test
+ public void testGetMessage() {
+ String msg = "abc";
+ when(mKeyguardMessageArea.getText()).thenReturn(msg);
+ assertThat(mMessageAreaController.getMessage()).isEqualTo(msg);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index b369098..ffd95f4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -31,6 +31,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -118,4 +119,14 @@
keyguardPasswordViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
}
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(
+ mKeyguardMessageAreaController,
+ never()
+ ).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 9eff704..b3d1c8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -33,6 +33,7 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@SmallTest
@@ -112,4 +113,14 @@
mKeyguardPatternViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
}
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(
+ mKeyguardMessageAreaController,
+ never()
+ ).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d9efdea..8bcfe6f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -100,4 +100,12 @@
pinViewController.startAppearAnimation()
verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
}
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ pinViewController.startAppearAnimation()
+ verify(keyguardMessageAreaController, Mockito.never())
+ .setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index aa4469f..4d58b09 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -548,6 +548,22 @@
verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null);
}
+ @Test
+ public void onDensityorFontScaleChanged() {
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+ configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+ ConfigurationController.ConfigurationListener.class);
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+ configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
+
+ verify(mView).onDensityOrFontScaleChanged();
+ verify(mKeyguardSecurityViewFlipperController).onDensityOrFontScaleChanged();
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class));
+ }
+
+
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached();
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 1bd14e5..36ed669 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -262,9 +262,12 @@
ConstraintSet.Constraint userSwitcherConstraint =
getViewConstraint(R.id.keyguard_bouncer_user_switcher);
- assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.topToBottom).isEqualTo(
+ R.id.keyguard_bouncer_user_switcher);
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.bottomToTop).isEqualTo(
+ mSecurityViewFlipper.getId());
assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo(
getContext().getResources().getDimensionPixelSize(
R.dimen.bouncer_user_switcher_y_trans));
@@ -308,6 +311,17 @@
}
@Test
+ public void testOnDensityOrFontScaleChanged() {
+ setupUserSwitcher();
+ View oldUserSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ mKeyguardSecurityContainer.onDensityOrFontScaleChanged();
+ View newUserSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(oldUserSwitcher).isNotEqualTo(newUserSwitcher);
+ }
+
+ @Test
public void testTouchesAreRecognizedAsBeingOnTheOtherSideOfSecurity() {
setupUserSwitcher();
setViewWidth(VIEW_WIDTH);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 9296d3d..fd02ac9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -106,4 +106,10 @@
}
}
}
+
+ @Test
+ public void onDensityOrFontScaleChanged() {
+ mKeyguardSecurityViewFlipperController.onDensityOrFontScaleChanged();
+ verify(mView).removeAllViews();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 717aa66..7231b34 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -29,6 +29,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.google.common.truth.Truth.assertThat;
@@ -36,6 +37,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -89,6 +91,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.service.dreams.IDreamManager;
+import android.service.trust.TrustAgentService;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -113,6 +116,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -157,6 +161,8 @@
private static final int FINGERPRINT_SENSOR_ID = 1;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private DumpManager mDumpManager;
@Mock
private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
@@ -306,8 +312,7 @@
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(SubscriptionManager::getDefaultSubscriptionId);
KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
- ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
- .when(ActivityManager::getCurrentUser);
+ when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
@@ -351,7 +356,9 @@
@After
public void tearDown() {
- mMockitoSession.finishMocking();
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
cleanupKeyguardUpdateMonitor();
}
@@ -629,7 +636,7 @@
@Test
public void testNoStartAuthenticate_whenAboutToShowBouncer() {
- mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(
+ mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(
/* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false);
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -1032,6 +1039,7 @@
@Test
public void testSecondaryLockscreenRequirement() {
KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+ when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
int user = KeyguardUpdateMonitor.getCurrentUser();
String packageName = "fake.test.package";
String cls = "FakeService";
@@ -1313,7 +1321,10 @@
Arrays.asList("Unlocked by wearable"));
// THEN the showTrustGrantedMessage should be called with the first message
- verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable");
+ verify(mTestCallback).onTrustGrantedForCurrentUser(
+ anyBoolean(),
+ eq(new TrustGrantFlags(0)),
+ eq("Unlocked by wearable"));
}
@Test
@@ -1370,6 +1381,29 @@
}
@Test
+ public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ bouncerFullyVisibleAndNotGoingToSleep();
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ successfulFingerprintAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
cleanupKeyguardUpdateMonitor();
// This disables face auth
@@ -1723,6 +1757,155 @@
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() {
+ // GIVEN device is interactive
+ deviceIsInteractive();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_doesNotDismiss() {
+ // GIVEN device is NOT interactive
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(false) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_temporaryAndRenewable() {
+ // GIVEN device is interactive
+ deviceIsInteractive();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged for a different user
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ 546 /* userId, not the current userId */,
+ 0 /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ anyBoolean() /* dismissKeyguard */,
+ anyObject() /* flags */,
+ anyString() /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGranted_differentUser_noCallback() {
+ // GIVEN device is interactive
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD AND TRUST_TEMPORARY_AND_RENEWABLE
+ // flags (temporary & rewable is active unlock)
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_bouncerShowing_initiatedByUser() {
+ // GIVEN device is interactive & bouncer is showing
+ deviceIsInteractive();
+ bouncerFullyVisible();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with INITIATED_BY_USER flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId, not the current userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER)),
+ anyString() /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_bouncerShowing_temporaryRenewable() {
+ // GIVEN device is NOT interactive & bouncer is showing
+ bouncerFullyVisible();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with INITIATED_BY_USER flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId, not the current userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+ anyString() /* message */
+ );
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1774,6 +1957,15 @@
.onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
}
+ private void successfulFingerprintAuth() {
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+ .onAuthenticationSucceeded(
+ new FingerprintManager.AuthenticationResult(null,
+ null,
+ mCurrentUserId,
+ true));
+ }
+
private void triggerSuccessfulFaceAuth() {
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager).authenticate(any(),
@@ -1851,7 +2043,7 @@
}
private void setKeyguardBouncerVisibility(boolean isVisible) {
- mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
+ mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
mTestableLooper.processAllMessages();
}
@@ -1883,7 +2075,7 @@
AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
protected TestableKeyguardUpdateMonitor(Context context) {
- super(context,
+ super(context, mUserTracker,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index ff4412e9..7a5b772 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -30,15 +31,15 @@
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
-import androidx.lifecycle.MutableLiveData;
-
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.settings.CurrentUserObservable;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -52,8 +53,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-// Need to run tests on main looper because LiveData operations such as setData, observe,
-// removeObserver cannot be invoked on a background thread.
+// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
@RunWithLooper(setAsMainLooper = true)
public final class ClockManagerTest extends SysuiTestCase {
@@ -63,14 +63,16 @@
private static final int SECONDARY_USER_ID = 11;
private static final Uri SETTINGS_URI = null;
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private ClockManager mClockManager;
private ContentObserver mContentObserver;
private DockManagerFake mFakeDockManager;
- private MutableLiveData<Integer> mCurrentUser;
+ private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
@Mock PluginManager mMockPluginManager;
@Mock SysuiColorExtractor mMockColorExtractor;
@Mock ContentResolver mMockContentResolver;
- @Mock CurrentUserObservable mMockCurrentUserObserable;
+ @Mock UserTracker mUserTracker;
@Mock SettingsWrapper mMockSettingsWrapper;
@Mock ClockManager.ClockChangedListener mMockListener1;
@Mock ClockManager.ClockChangedListener mMockListener2;
@@ -83,18 +85,18 @@
mFakeDockManager = new DockManagerFake();
- mCurrentUser = new MutableLiveData<>();
- mCurrentUser.setValue(MAIN_USER_ID);
- when(mMockCurrentUserObserable.getCurrentUser()).thenReturn(mCurrentUser);
+ when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
+ mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
mClockManager = new ClockManager(getContext(), inflater,
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
- mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
+ mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
mClockManager.addBuiltinClock(() -> new BubbleClockController(
getContext().getResources(), inflater, mMockColorExtractor));
mClockManager.addOnClockChangedListener(mMockListener1);
mClockManager.addOnClockChangedListener(mMockListener2);
+ verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
reset(mMockListener1, mMockListener2);
mContentObserver = mClockManager.getContentObserver();
@@ -221,7 +223,7 @@
@Test
public void onUserChanged_defaultClock() {
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is null for the default clock face
assertThat(mClockManager.getCurrentClock()).isNull();
}
@@ -232,7 +234,7 @@
when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
BUBBLE_CLOCK);
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@@ -244,8 +246,13 @@
// AND the second user as selected the bubble clock for the dock
when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
+
+ private void switchUser(int newUser) {
+ when(mUserTracker.getUserId()).thenReturn(newUser);
+ mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
index 6b1ef38..81d0034 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -3,6 +3,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.content.pm.UserInfo
import android.content.res.Resources
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -11,9 +12,11 @@
import com.android.systemui.flags.FlagListenable
import com.android.systemui.flags.Flags
import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
@@ -26,9 +29,9 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -44,6 +47,8 @@
private lateinit var chooserSelector: ChooserSelector
@Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockProfileContext: Context
+ @Mock private lateinit var mockUserTracker: UserTracker
@Mock private lateinit var mockPackageManager: PackageManager
@Mock private lateinit var mockResources: Resources
@Mock private lateinit var mockFeatureFlags: FeatureFlags
@@ -52,12 +57,20 @@
fun setup() {
MockitoAnnotations.initMocks(this)
- `when`(mockContext.packageManager).thenReturn(mockPackageManager)
- `when`(mockContext.resources).thenReturn(mockResources)
- `when`(mockResources.getString(anyInt())).thenReturn(
+ whenever(mockContext.createContextAsUser(any(), anyInt())).thenReturn(mockProfileContext)
+ whenever(mockContext.resources).thenReturn(mockResources)
+ whenever(mockProfileContext.packageManager).thenReturn(mockPackageManager)
+ whenever(mockResources.getString(anyInt())).thenReturn(
ComponentName("TestPackage", "TestClass").flattenToString())
+ whenever(mockUserTracker.userProfiles).thenReturn(listOf(UserInfo(), UserInfo()))
- chooserSelector = ChooserSelector(mockContext, mockFeatureFlags, testScope, testDispatcher)
+ chooserSelector = ChooserSelector(
+ mockContext,
+ mockUserTracker,
+ mockFeatureFlags,
+ testScope,
+ testDispatcher,
+ )
}
@After
@@ -74,7 +87,9 @@
// Assert
verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+ flagListener.capture(),
+ )
verify(mockFeatureFlags, never()).removeListener(any())
// Act
@@ -87,86 +102,102 @@
@Test
fun initialize_enablesUnbundledChooser_whenFlagEnabled() {
// Arrange
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
// Act
chooserSelector.start()
// Assert
- verify(mockPackageManager).setComponentEnabledSetting(
+ verify(mockPackageManager, times(2)).setComponentEnabledSetting(
eq(ComponentName("TestPackage", "TestClass")),
eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
- anyInt())
+ anyInt(),
+ )
}
@Test
fun initialize_disablesUnbundledChooser_whenFlagDisabled() {
// Arrange
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
// Act
chooserSelector.start()
// Assert
- verify(mockPackageManager).setComponentEnabledSetting(
+ verify(mockPackageManager, times(2)).setComponentEnabledSetting(
eq(ComponentName("TestPackage", "TestClass")),
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- anyInt())
+ anyInt(),
+ )
}
@Test
fun enablesUnbundledChooser_whenFlagBecomesEnabled() {
// Arrange
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
chooserSelector.start()
verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+ flagListener.capture(),
+ )
verify(mockPackageManager, never()).setComponentEnabledSetting(
- any(), eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED), anyInt())
+ any(),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ anyInt(),
+ )
// Act
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
// Assert
- verify(mockPackageManager).setComponentEnabledSetting(
+ verify(mockPackageManager, times(2)).setComponentEnabledSetting(
eq(ComponentName("TestPackage", "TestClass")),
eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
- anyInt())
+ anyInt(),
+ )
}
@Test
fun disablesUnbundledChooser_whenFlagBecomesDisabled() {
// Arrange
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
chooserSelector.start()
verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+ flagListener.capture(),
+ )
verify(mockPackageManager, never()).setComponentEnabledSetting(
- any(), eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED), anyInt())
+ any(),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ anyInt(),
+ )
// Act
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
// Assert
- verify(mockPackageManager).setComponentEnabledSetting(
+ verify(mockPackageManager, times(2)).setComponentEnabledSetting(
eq(ComponentName("TestPackage", "TestClass")),
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- anyInt())
+ anyInt(),
+ )
}
@Test
fun doesNothing_whenAnotherFlagChanges() {
// Arrange
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
chooserSelector.start()
verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+ flagListener.capture(),
+ )
clearInvocations(mockPackageManager)
// Act
- `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
// Assert
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
index a4e0825..5886206 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui
+import android.content.Context
import android.graphics.Canvas
import android.graphics.Insets
import android.graphics.Path
@@ -48,6 +49,7 @@
@Mock private lateinit var mockCanvas: Canvas
@Mock private lateinit var mockRootView: View
@Mock private lateinit var mockDisplay: Display
+ @Mock private lateinit var mockContext: Context
private lateinit var cutoutBaseView: DisplayCutoutBaseView
private val cutout: DisplayCutout = DisplayCutout.Builder()
@@ -168,7 +170,9 @@
R.bool.config_fillMainBuiltInDisplayCutout, fillCutout)
cutoutBaseView = spy(DisplayCutoutBaseView(mContext))
- whenever(cutoutBaseView.display).thenReturn(mockDisplay)
+
+ whenever(cutoutBaseView.context).thenReturn(mockContext)
+ whenever(mockContext.display).thenReturn(mockDisplay)
whenever(mockDisplay.uniqueId).thenReturn("mockDisplayUniqueId")
whenever(cutoutBaseView.rootView).thenReturn(mockRootView)
whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
index 054650b..8207fa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui
+import android.content.Context
import android.graphics.Insets
import android.graphics.PixelFormat
import android.graphics.Rect
@@ -44,6 +45,7 @@
@Mock private lateinit var mockDisplay: Display
@Mock private lateinit var mockRootView: View
+ @Mock private lateinit var mockContext: Context
private val displayWidth = 100
private val displayHeight = 200
@@ -75,7 +77,8 @@
decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport))
whenever(decorHwcLayer.width).thenReturn(displayWidth)
whenever(decorHwcLayer.height).thenReturn(displayHeight)
- whenever(decorHwcLayer.display).thenReturn(mockDisplay)
+ whenever(decorHwcLayer.context).thenReturn(mockContext)
+ whenever(mockContext.display).thenReturn(mockDisplay)
whenever(decorHwcLayer.rootView).thenReturn(mockRootView)
whenever(mockRootView.left).thenReturn(0)
whenever(mockRootView.top).thenReturn(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 69ccc8b..57ca9c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -903,6 +903,97 @@
}
@Test
+ public void changeMagnificationSize_expectedWindowSize() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final float magnificationScaleLarge = 2.5f;
+ final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
+ final int magnificationSize = (int) (initSize * magnificationScaleLarge);
+
+ final int expectedWindowHeight = magnificationSize;
+ final int expectedWindowWidth = magnificationSize;
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.changeMagnificationSize(
+ WindowMagnificationSettings.MagnificationSize.LARGE);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ assertEquals(expectedWindowHeight, actualWindowHeight.get());
+ assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ }
+
+ @Test
+ public void editModeOnDragCorner_resizesWindow() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final int startingSize = (int) (bounds.width() / 2);
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ waitForIdleSync();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController
+ .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ assertEquals(startingSize + 1, actualWindowHeight.get());
+ assertEquals(startingSize + 2, actualWindowWidth.get());
+ }
+
+ @Test
+ public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final int startingSize = (int) (bounds.width() / 2f);
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ mWindowMagnificationController
+ .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+ assertEquals(startingSize + 1, actualWindowHeight.get());
+ assertEquals(startingSize, actualWindowWidth.get());
+ }
+
+ @Test
public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
final int minimumWindowSize = mResources.getDimensionPixelSize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index d0bd4f7..b2c5266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -32,8 +32,10 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,6 +46,7 @@
@SmallTest
public class MenuAnimationControllerTest extends SysuiTestCase {
+ private boolean mLastIsMoveToTucked;
private ViewPropertyAnimator mViewPropertyAnimator;
private MenuView mMenuView;
private MenuAnimationController mMenuAnimationController;
@@ -60,6 +63,14 @@
doReturn(mViewPropertyAnimator).when(mMenuView).animate();
mMenuAnimationController = new MenuAnimationController(mMenuView);
+ mLastIsMoveToTucked = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+ mLastIsMoveToTucked);
}
@Test
@@ -81,10 +92,34 @@
@Test
public void startGrowAnimation_menuCompletelyOpaque() {
- mMenuAnimationController.startShrinkAnimation(null);
+ mMenuAnimationController.startShrinkAnimation(/* endAction= */ null);
mMenuAnimationController.startGrowAnimation();
assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f);
}
+
+ @Test
+ public void moveToEdgeAndHide_untucked_expectedSharedPreferenceValue() {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */
+ false);
+
+ mMenuAnimationController.moveToEdgeAndHide();
+ final boolean isMoveToTucked = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
+
+ assertThat(isMoveToTucked).isTrue();
+ }
+
+ @Test
+ public void moveOutEdgeAndShow_tucked_expectedSharedPreferenceValue() {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */
+ true);
+
+ mMenuAnimationController.moveOutEdgeAndShow();
+ final boolean isMoveToTucked = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ true);
+
+ assertThat(isMoveToTucked).isFalse();
+ }
}
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 d20eeaf..428a00a 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
@@ -18,23 +18,44 @@
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.systemBars;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -43,12 +64,34 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
+
/** Tests for {@link MenuViewLayer}. */
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class MenuViewLayerTest extends SysuiTestCase {
+ private static final String SELECT_TO_SPEAK_PACKAGE_NAME = "com.google.android.marvin.talkback";
+ private static final String SELECT_TO_SPEAK_SERVICE_NAME =
+ "com.google.android.accessibility.selecttospeak.SelectToSpeakService";
+ private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName(
+ SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
+
+ private static final int DISPLAY_WINDOW_WIDTH = 1080;
+ private static final int DISPLAY_WINDOW_HEIGHT = 2340;
+ private static final int STATUS_BAR_HEIGHT = 75;
+ private static final int NAVIGATION_BAR_HEIGHT = 125;
+ private static final int IME_HEIGHT = 350;
+ private static final int IME_TOP =
+ DISPLAY_WINDOW_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT - IME_HEIGHT;
+
private MenuViewLayer mMenuViewLayer;
+ private String mLastAccessibilityButtonTargets;
+ private String mLastEnabledAccessibilityServices;
+ private WindowMetrics mWindowMetrics;
+ private MenuView mMenuView;
+ private MenuAnimationController mMenuAnimationController;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -56,13 +99,50 @@
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
+ @Mock
+ private WindowManager mStubWindowManager;
+
+ @Mock
+ private AccessibilityManager mStubAccessibilityManager;
+
@Before
public void setUp() throws Exception {
- final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
- AccessibilityManager.class);
- mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+ final Rect mDisplayBounds = new Rect();
+ mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
+ DISPLAY_WINDOW_HEIGHT);
+ mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets()));
+ doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
+
+ mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
mFloatingMenu);
+ mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
+ mMenuAnimationController = mMenuView.getMenuAnimationController();
+
+ mLastAccessibilityButtonTargets =
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+ mLastEnabledAccessibilityServices =
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
+
+ mMenuViewLayer.onAttachedToWindow();
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
+ UserHandle.USER_CURRENT);
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
+ UserHandle.USER_CURRENT);
+
+ mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
+ mMenuViewLayer.onDetachedFromWindow();
}
@Test
@@ -87,4 +167,110 @@
verify(mFloatingMenu).hide();
}
+
+ @Test
+ public void tiggerDismissMenuAction_matchA11yButtonTargetsResult() {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ MAGNIFICATION_COMPONENT_NAME.flattenToString(), UserHandle.USER_CURRENT);
+
+ mMenuViewLayer.mDismissMenuAction.run();
+ final String value =
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+
+ assertThat(value).isEqualTo("");
+ }
+
+ @Test
+ public void tiggerDismissMenuAction_matchEnabledA11yServicesResult() {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ serviceInfo.applicationInfo = applicationInfo;
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+ final AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+ accessibilityServiceInfo.setResolveInfo(resolveInfo);
+ accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ final List<AccessibilityServiceInfo> serviceInfoList = new ArrayList<>();
+ accessibilityServiceInfo.setComponentName(TEST_SELECT_TO_SPEAK_COMPONENT_NAME);
+ serviceInfoList.add(accessibilityServiceInfo);
+ when(mStubAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK)).thenReturn(serviceInfoList);
+
+ mMenuViewLayer.mDismissMenuAction.run();
+ final String value = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+ assertThat(value).isEqualTo("");
+ }
+
+ @Test
+ public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
+ final float menuTop = STATUS_BAR_HEIGHT + 100;
+ mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+
+ dispatchShowingImeInsets();
+
+ assertThat(mMenuView.getTranslationX()).isEqualTo(0);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
+ }
+
+ @Test
+ public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
+ final float menuTop = IME_TOP + 100;
+ mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+
+ dispatchShowingImeInsets();
+
+ final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
+ assertThat(mMenuView.getTranslationX()).isEqualTo(0);
+ assertThat(menuBottom).isLessThan(IME_TOP);
+ }
+
+ @Test
+ public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
+ final float menuTop = IME_TOP + 200;
+ mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+ dispatchShowingImeInsets();
+
+ dispatchHidingImeInsets();
+
+ assertThat(mMenuView.getTranslationX()).isEqualTo(0);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
+ }
+
+ private void dispatchShowingImeInsets() {
+ final WindowInsets fakeShowingImeInsets = fakeImeInsets(/* isImeVisible= */ true);
+ doReturn(fakeShowingImeInsets).when(mWindowMetrics).getWindowInsets();
+ mMenuViewLayer.dispatchApplyWindowInsets(fakeShowingImeInsets);
+ }
+
+ private void dispatchHidingImeInsets() {
+ final WindowInsets fakeHidingImeInsets = fakeImeInsets(/* isImeVisible= */ false);
+ doReturn(fakeHidingImeInsets).when(mWindowMetrics).getWindowInsets();
+ mMenuViewLayer.dispatchApplyWindowInsets(fakeHidingImeInsets);
+ }
+
+ private WindowInsets fakeDisplayInsets() {
+ return new WindowInsets.Builder()
+ .setVisible(systemBars() | displayCutout(), /* visible= */ true)
+ .setInsets(systemBars() | displayCutout(),
+ Insets.of(/* left= */ 0, STATUS_BAR_HEIGHT, /* right= */ 0,
+ NAVIGATION_BAR_HEIGHT))
+ .build();
+ }
+
+ private WindowInsets fakeImeInsets(boolean isImeVisible) {
+ final int bottom = isImeVisible ? (IME_HEIGHT + NAVIGATION_BAR_HEIGHT) : 0;
+ return new WindowInsets.Builder()
+ .setVisible(ime(), isImeVisible)
+ .setInsets(ime(),
+ Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom))
+ .build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
new file mode 100644
index 0000000..982f033
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class AccessorizedBatteryDrawableTest : SysuiTestCase() {
+ @Test
+ fun intrinsicSize_shieldFalse_isBatterySize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = false
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt())
+ }
+
+ @Test
+ fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = true
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight)
+ .isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt())
+ }
+
+ // TODO(b/255625888): Screenshot tests for this drawable would be amazing!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1d038a4..1482f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -34,7 +34,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -50,15 +52,16 @@
private BatteryMeterView mBatteryMeterView;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private ConfigurationController mConfigurationController;
@Mock
private TunerService mTunerService;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
private Handler mHandler;
@Mock
private ContentResolver mContentResolver;
+ private FakeFeatureFlags mFeatureFlags;
@Mock
private BatteryController mBatteryController;
@@ -71,19 +74,13 @@
when(mBatteryMeterView.getContext()).thenReturn(mContext);
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
- mController = new BatteryMeterViewController(
- mBatteryMeterView,
- mConfigurationController,
- mTunerService,
- mBroadcastDispatcher,
- mHandler,
- mContentResolver,
- mBatteryController
- );
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
}
@Test
public void onViewAttached_callbacksRegistered() {
+ initController();
mController.onViewAttached();
verify(mConfigurationController).addCallback(any());
@@ -101,6 +98,7 @@
@Test
public void onViewDetached_callbacksUnregistered() {
+ initController();
// Set everything up first.
mController.onViewAttached();
@@ -114,6 +112,7 @@
@Test
public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
+ initController();
// Start out receiving tuner updates
mController.onViewAttached();
@@ -124,10 +123,43 @@
@Test
public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
+ initController();
+
mController.ignoreTunerUpdates();
mController.onViewAttached();
verify(mTunerService, never()).addTunable(any(), any());
}
+
+ @Test
+ public void shieldFlagDisabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(false);
+ }
+
+ @Test
+ public void shieldFlagEnabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(true);
+ }
+
+ private void initController() {
+ mController = new BatteryMeterViewController(
+ mBatteryMeterView,
+ mUserTracker,
+ mConfigurationController,
+ mTunerService,
+ mHandler,
+ mContentResolver,
+ mFeatureFlags,
+ mBatteryController
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index b4ff2a5..eb7d9c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -17,7 +17,9 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.widget.ImageView
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
@@ -58,6 +60,182 @@
// No assert needed
}
+ @Test
+ fun contentDescription_unknown() {
+ mBatteryMeterView.onBatteryUnknownStateChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_unknown)
+ )
+ }
+
+ @Test
+ fun contentDescription_estimate() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_estimateAndOverheated() {
+ mBatteryMeterView.onBatteryLevelChanged(17, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 17,
+ ESTIMATE,
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_overheated() {
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+ }
+
+ @Test
+ fun contentDescription_charging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 45)
+ )
+ }
+
+ @Test
+ fun contentDescription_notCharging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, false)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 45)
+ )
+ }
+
+ @Test
+ fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+
+ // Update the show mode from estimate to percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+ assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 15)
+ )
+ }
+
+ @Test
+ fun contentDescription_manyUpdates_alwaysUpdated() {
+ // Overheated
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+
+ // Overheated & estimate
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+ mBatteryMeterView.updatePercentText()
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just estimate
+ mBatteryMeterView.onIsOverheatedChanged(false)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 90)
+ )
+
+ // Charging
+ mBatteryMeterView.onBatteryLevelChanged(90, true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 90)
+ )
+ }
+
+ @Test
+ fun isOverheatedChanged_true_drawableGetsTrue() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isTrue()
+ }
+
+ @Test
+ fun isOverheatedChanged_false_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ // Start as true
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ // Update to false
+ mBatteryMeterView.onIsOverheatedChanged(false)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ @Test
+ fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(false)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
+ return (mBatteryMeterView.getChildAt(0) as ImageView)
+ .drawable as AccessorizedBatteryDrawable
+ }
+
private class Fetcher : BatteryEstimateFetcher {
override fun fetchBatteryTimeRemainingEstimate(
completion: EstimateFetchCompletion) {
@@ -68,4 +246,4 @@
private companion object {
const val ESTIMATE = "2 hours 2 minutes"
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
new file mode 100644
index 0000000..39cb047
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class BatterySpecsTest : SysuiTestCase() {
+ @Test
+ fun getFullBatteryHeight_shieldFalse_returnsMainHeight() {
+ val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false)
+
+ assertThat(fullHeight).isEqualTo(56f)
+ }
+
+ @Test
+ fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() {
+ val mainHeight = BATTERY_HEIGHT * 5
+ val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true)
+
+ // Since the main battery was scaled 5x, the output height should also be scaled 5x
+ val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5
+
+ assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldFalse_returnsMainWidth() {
+ val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false)
+
+ assertThat(fullWidth).isEqualTo(33f)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() {
+ val mainWidth = BATTERY_WIDTH * 3.3f
+
+ val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true)
+
+ // Since the main battery was scaled 3.3x, the output width should also be scaled 5x
+ val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f
+ assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldFalse_returnsFullHeight() {
+ val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false)
+
+ assertThat(mainHeight).isEqualTo(89f)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() {
+ val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f
+
+ val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true)
+
+ // Since the full height was scaled 7.7x, the main height should also be scaled 7.7x.
+ val expectedHeight = BATTERY_HEIGHT * 7.7f
+ assertThat(mainHeight).isWithin(.0001f).of(expectedHeight)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldFalse_returnsFullWidth() {
+ val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false)
+
+ assertThat(mainWidth).isEqualTo(2345f)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() {
+ val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true)
+
+ // Since the full width was scaled 0.6x, the main height should also be scaled 0.6x.
+ val expectedWidth = BATTERY_WIDTH * 0.6f
+ assertThat(mainWidth).isWithin(.0001f).of(expectedWidth)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 45b8ce1..898f370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -125,6 +125,21 @@
}
@Test
+ fun testCredentialPasswordDismissesOnBack() {
+ val container = initializeCredentialPasswordContainer(addToView = true)
+ assertThat(container.parent).isNotNull()
+ val root = container.rootView
+
+ // Simulate back invocation
+ container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+ container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+ waitForIdleSync()
+
+ assertThat(container.parent).isNull()
+ assertThat(root.isAttachedToWindow).isFalse()
+ }
+
+ @Test
fun testIgnoresAnimatedInWhenDismissed() {
val container = initializeFingerprintContainer(addToView = false)
container.dismissFromSystemServer()
@@ -175,6 +190,25 @@
}
@Test
+ fun testFocusLossAfterRotating() {
+ val container = initializeFingerprintContainer()
+ waitForIdleSync()
+
+ val requestID = authContainer?.requestId ?: 0L
+
+ verify(callback).onDialogAnimatedIn(requestID)
+ container.onOrientationChanged()
+ container.onWindowFocusChanged(false)
+ waitForIdleSync()
+
+ verify(callback, never()).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(requestID)
+ )
+ }
+
+ @Test
fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
@@ -350,20 +384,7 @@
@Test
fun testCredentialUI_disablesClickingOnBackground() {
- whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
- whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- )
-
- // In the credential view, clicking on the background (to cancel authentication) is not
- // valid. Thus, the listener should be null, and it should not be in the accessibility
- // hierarchy.
- val container = initializeFingerprintContainer(
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
- )
- waitForIdleSync()
-
- assertThat(container.hasCredentialPasswordView()).isTrue()
+ val container = initializeCredentialPasswordContainer()
assertThat(container.hasBiometricPrompt()).isFalse()
assertThat(
container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -423,6 +444,27 @@
verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
}
+ private fun initializeCredentialPasswordContainer(
+ addToView: Boolean = true,
+ ): TestAuthContainerView {
+ whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ )
+
+ // In the credential view, clicking on the background (to cancel authentication) is not
+ // valid. Thus, the listener should be null, and it should not be in the accessibility
+ // hierarchy.
+ val container = initializeFingerprintContainer(
+ authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+ addToView = addToView,
+ )
+ waitForIdleSync()
+
+ assertThat(container.hasCredentialPasswordView()).isTrue()
+ return container
+ }
+
private fun initializeFingerprintContainer(
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
addToView: Boolean = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 6ec5a6c..4b459c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,7 +43,7 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -106,7 +106,7 @@
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
@Mock private lateinit var featureFlags: FeatureFlags
- @Mock private lateinit var bouncerInteractor: BouncerInteractor
+ @Mock private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -141,7 +141,7 @@
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
- bouncerInteractor, isDebuggable
+ mPrimaryBouncerInteractor, isDebuggable
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index aa14a40..acdafe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -73,7 +73,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -114,10 +114,8 @@
@Rule
public MockitoRule rule = MockitoJUnit.rule();
-
// Unit under test
private UdfpsController mUdfpsController;
-
// Dependencies
private FakeExecutor mBiometricsExecutor;
@Mock
@@ -171,7 +169,6 @@
private UdfpsDisplayMode mUdfpsDisplayMode;
@Mock
private FeatureFlags mFeatureFlags;
-
// Stuff for configuring mocks
@Mock
private UdfpsView mUdfpsView;
@@ -192,7 +189,7 @@
@Mock
private AlternateUdfpsTouchProvider mAlternateTouchProvider;
@Mock
- private BouncerInteractor mBouncerInteractor;
+ private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
// Capture listeners so that they can be used to send events
@Captor
@@ -249,54 +246,42 @@
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
true /* resetLockoutRequiresHardwareAuthToken */);
- List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(mOpticalProps);
- props.add(mUltrasonicProps);
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
-
mFgExecutor = new FakeExecutor(new FakeSystemClock());
// Create a fake background executor.
mBiometricsExecutor = new FakeExecutor(new FakeSystemClock());
- mUdfpsController = new UdfpsController(
- mContext,
- execution,
- mLayoutInflater,
- mFingerprintManager,
- mWindowManager,
- mStatusBarStateController,
- mFgExecutor,
- new ShadeExpansionStateManager(),
- mStatusBarKeyguardViewManager,
- mDumpManager,
- mKeyguardUpdateMonitor,
- mFeatureFlags,
- mFalsingManager,
- mPowerManager,
- mAccessibilityManager,
- mLockscreenShadeTransitionController,
- mScreenLifecycle,
- mVibrator,
- mUdfpsHapticsSimulator,
- mUdfpsShell,
- mKeyguardStateController,
- mDisplayManager,
- mHandler,
- mConfigurationController,
- mSystemClock,
- mUnlockedScreenOffAnimationController,
- mSystemUIDialogManager,
- mLatencyTracker,
- mActivityLaunchAnimator,
- Optional.of(mAlternateTouchProvider),
- mBiometricsExecutor,
- mBouncerInteractor);
+ initUdfpsController(true /* hasAlternateTouchProvider */);
+ }
+
+ private void initUdfpsController(boolean hasAlternateTouchProvider) {
+ initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
+ }
+
+ private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps,
+ boolean hasAlternateTouchProvider) {
+ reset(mFingerprintManager);
+ reset(mScreenLifecycle);
+
+ final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider =
+ hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty();
+
+ mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
+ mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
+ new ShadeExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager,
+ mKeyguardUpdateMonitor, mFeatureFlags, mFalsingManager, mPowerManager,
+ mAccessibilityManager, mLockscreenShadeTransitionController, mScreenLifecycle,
+ mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, mKeyguardStateController,
+ mDisplayManager, mHandler, mConfigurationController, mSystemClock,
+ mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
+ mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor,
+ mPrimaryBouncerInteractor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
mScreenObserver = mScreenObserverCaptor.getValue();
- mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams());
+
+ mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
}
@@ -333,8 +318,7 @@
}
@Test
- public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice()
- throws RemoteException {
+ public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException {
onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */);
}
@@ -405,14 +389,14 @@
// GIVEN overlay was showing and the udfps bouncer is showing
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
// WHEN the overlay is hidden
mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
mFgExecutor.runAllReady();
// THEN the udfps bouncer is reset
- verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true));
+ verify(mStatusBarKeyguardViewManager).hideAlternateBouncer(eq(true));
}
@Test
@@ -521,8 +505,37 @@
new MotionEvent.PointerCoords[]{pc}, 0, 0, 1f, 1f, 0, 0, 0, 0);
}
+ private static class TestParams {
+ public final FingerprintSensorPropertiesInternal sensorProps;
+ public final boolean hasAlternateTouchProvider;
+
+ TestParams(FingerprintSensorPropertiesInternal sensorProps,
+ boolean hasAlternateTouchProvider) {
+ this.sensorProps = sensorProps;
+ this.hasAlternateTouchProvider = hasAlternateTouchProvider;
+ }
+ }
+
+ private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) {
+ for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
+ mUltrasonicProps)) {
+ for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) {
+ initUdfpsController(sensorProps, hasAlternateTouchProvider);
+ testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider));
+ }
+ }
+ }
+
@Test
- public void onTouch_propagatesTouchInNativeOrientationAndResolution() throws RemoteException {
+ public void onTouch_propagatesTouchInNativeOrientationAndResolution() {
+ runWithAllParams(
+ this::onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized);
+ }
+
+ private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized(
+ TestParams testParams) throws RemoteException {
+ reset(mUdfpsView);
+
final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner.
final int displayWidth = 1080;
final int displayHeight = 1920;
@@ -541,13 +554,13 @@
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// Show the overlay.
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
// Test ROTATION_0
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_0));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
@@ -559,12 +572,19 @@
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
+ }
// Test ROTATION_90
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ reset(mFingerprintManager);
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_90));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
@@ -575,12 +595,19 @@
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
+ }
// Test ROTATION_270
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ reset(mFingerprintManager);
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_270));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
@@ -591,12 +618,19 @@
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
+ }
// Test ROTATION_180
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ reset(mFingerprintManager);
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_180));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
@@ -608,26 +642,22 @@
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
- }
-
- private void runForAllUdfpsTypes(
- ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) {
- for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
- mUltrasonicProps)) {
- mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
- sensorPropsConsumer.accept(sensorProps);
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
}
}
@Test
public void fingerDown() {
- runForAllUdfpsTypes(this::fingerDownForSensor);
+ runWithAllParams(this::fingerDownParameterized);
}
- private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void fingerDownParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
mKeyguardUpdateMonitor);
@@ -637,7 +667,7 @@
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -655,14 +685,22 @@
mFgExecutor.runAllReady();
- // THEN FingerprintManager is notified about onPointerDown
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
- eq(0f));
- verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
- anyFloat(), anyFloat());
+ // THEN the touch provider is notified about onPointerDown.
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
+ eq(0f));
+ verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyInt(), anyFloat(), anyFloat());
+ verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f));
+ verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(),
+ anyFloat(), anyFloat());
+ }
// AND display configuration begins
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
} else {
@@ -671,16 +709,27 @@
verify(mUdfpsView, never()).configureDisplay(any());
}
verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
- verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// AND onDisplayConfigured notifies FingerprintManager about onUiReady
mOnDisplayConfiguredCaptor.getValue().run();
mBiometricsExecutor.runAllReady();
- InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
- inOrder.verify(mAlternateTouchProvider).onUiReady();
- inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ if (testParams.hasAlternateTouchProvider) {
+ InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
+ inOrder.verify(mAlternateTouchProvider).onUiReady();
+ inOrder.verify(mLatencyTracker).onActionEnd(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+ } else {
+ InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
+ inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId));
+ inOrder.verify(mLatencyTracker).onActionEnd(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mAlternateTouchProvider, never()).onUiReady();
+ }
} else {
+ verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
verify(mAlternateTouchProvider, never()).onUiReady();
verify(mLatencyTracker, never()).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
@@ -689,24 +738,23 @@
@Test
public void aodInterrupt() {
- runForAllUdfpsTypes(this::aodInterruptForSensor);
+ runWithAllParams(this::aodInterruptParameterized);
}
- private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void aodInterruptParameterized(TestParams testParams) throws RemoteException {
mUdfpsController.cancelAodInterrupt();
reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN that the overlay is showing and screen is on and fp is running
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN display configuration begins
// AND onDisplayConfigured notifies FingerprintManager about onUiReady
verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
@@ -715,29 +763,37 @@
verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture());
}
mBiometricsExecutor.runAllReady();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
- eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
- verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
- anyFloat(), anyFloat());
- verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0),
+ eq(3f) /* minor */, eq(2f) /* major */);
+ verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyInt(), anyFloat(), anyFloat());
+ verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */,
+ eq(2f) /* major */);
+ verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyFloat(), anyFloat());
+ }
}
@Test
public void cancelAodInterrupt() {
- runForAllUdfpsTypes(this::cancelAodInterruptForSensor);
+ runWithAllParams(this::cancelAodInterruptParameterized);
}
- private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void cancelAodInterruptParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
// WHEN it is cancelled
mUdfpsController.cancelAodInterrupt();
@@ -754,21 +810,20 @@
@Test
public void aodInterruptTimeout() {
- runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor);
+ runWithAllParams(this::aodInterruptTimeoutParameterized);
}
- private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void aodInterruptTimeoutParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
@@ -776,7 +831,7 @@
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN the display is unconfigured.
verify(mUdfpsView).unconfigureDisplay();
} else {
@@ -787,23 +842,23 @@
@Test
public void aodInterruptCancelTimeoutActionOnFingerUp() {
- runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor);
+ runWithAllParams(this::aodInterruptCancelTimeoutActionOnFingerUpParameterized);
}
- private void aodInterruptCancelTimeoutActionOnFingerUpForSensor(
- FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams)
+ throws RemoteException {
reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the ACTION_UP event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -833,7 +888,7 @@
moveEvent.recycle();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the finger up event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -844,7 +899,7 @@
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN the display should be unconfigured once. If the timeout action is not
// cancelled, the display would be unconfigured twice which would cause two
// FP attempts.
@@ -856,23 +911,23 @@
@Test
public void aodInterruptCancelTimeoutActionOnAcquired() {
- runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor);
+ runWithAllParams(this::aodInterruptCancelTimeoutActionOnAcquiredParameterized);
}
- private void aodInterruptCancelTimeoutActionOnAcquiredForSensor(
- FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ private void aodInterruptCancelTimeoutActionOnAcquiredParameterized(TestParams testParams)
+ throws RemoteException {
reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the acquired event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -880,7 +935,7 @@
}
// WHEN acquired is received
- mOverlayController.onAcquired(sensorProps.sensorId,
+ mOverlayController.onAcquired(testParams.sensorProps.sensorId,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
// Configure UdfpsView to accept the ACTION_DOWN event
@@ -900,7 +955,7 @@
moveEvent.recycle();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the finger up event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -911,7 +966,7 @@
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN the display should be unconfigured once. If the timeout action is not
// cancelled, the display would be unconfigured twice which would cause two
// FP attempts.
@@ -923,15 +978,14 @@
@Test
public void aodInterruptScreenOff() {
- runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor);
+ runWithAllParams(this::aodInterruptScreenOffParameterized);
}
- private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void aodInterruptScreenOffParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView);
// GIVEN screen off
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOff();
mFgExecutor.runAllReady();
@@ -945,17 +999,16 @@
@Test
public void aodInterrupt_fingerprintNotRunning() {
- runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor);
+ runWithAllParams(this::aodInterrupt_fingerprintNotRunningParameterized);
}
- private void aodInterrupt_fingerprintNotRunningForSensor(
- FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ private void aodInterrupt_fingerprintNotRunningParameterized(TestParams testParams)
+ throws RemoteException {
reset(mUdfpsView);
// GIVEN showing overlay
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
- BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
- mUdfpsOverlayControllerCallback);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index e5c7a42..3c61382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -30,7 +30,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
@@ -72,7 +72,7 @@
protected @Mock UdfpsController mUdfpsController;
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
protected @Mock KeyguardBouncer mBouncer;
- protected @Mock BouncerInteractor mBouncerInteractor;
+ protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
protected FakeSystemClock mSystemClock = new FakeSystemClock();
@@ -86,14 +86,19 @@
private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
protected List<ShadeExpansionListener> mExpansionListeners;
- private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
- mAltAuthInterceptorCaptor;
- protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateBouncer>
+ mAlternateBouncerCaptor;
+ protected StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallbackCaptor;
protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.KeyguardViewManagerCallback>
+ mKeyguardViewManagerCallbackArgumentCaptor;
+ protected StatusBarKeyguardViewManager.KeyguardViewManagerCallback mKeyguardViewManagerCallback;
+
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -131,9 +136,9 @@
}
protected void captureAltAuthInterceptor() {
- verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
- mAltAuthInterceptorCaptor.capture());
- mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+ verify(mStatusBarKeyguardViewManager).setAlternateBouncer(
+ mAlternateBouncerCaptor.capture());
+ mAlternateBouncer = mAlternateBouncerCaptor.getValue();
}
protected void captureKeyguardStateControllerCallback() {
@@ -143,15 +148,22 @@
}
public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
- return createUdfpsKeyguardViewController(false);
+ return createUdfpsKeyguardViewController(false, false);
+ }
+
+ public void captureKeyGuardViewManagerCallback() {
+ verify(mStatusBarKeyguardViewManager).addCallback(
+ mKeyguardViewManagerCallbackArgumentCaptor.capture());
+ mKeyguardViewManagerCallback = mKeyguardViewManagerCallbackArgumentCaptor.getValue();
}
protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
- boolean useModernBouncer) {
+ boolean useModernBouncer, boolean useExpandedOverlay) {
mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
- when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+ mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
+ when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
useModernBouncer ? null : mBouncer);
- return new UdfpsKeyguardViewController(
+ UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
mShadeExpansionStateManager,
@@ -167,6 +179,7 @@
mUdfpsController,
mActivityLaunchAnimator,
mFeatureFlags,
- mBouncerInteractor);
+ mPrimaryBouncerInteractor);
+ return controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 55b6194..babe533 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertFalse;
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.Mockito.atLeast;
@@ -30,6 +31,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -46,13 +48,14 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
- private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+ private @Captor ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback>
mBouncerExpansionCallbackCaptor;
- private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+ private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
@Override
public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
- return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
+ /* useExpandedOverlay */ false);
}
@Test
@@ -63,7 +66,7 @@
captureBouncerExpansionCallback();
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
mBouncerExpansionCallback.onVisibilityChanged(true);
assertTrue(mController.shouldPauseAuth());
@@ -239,13 +242,13 @@
sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
assertTrue(mController.shouldPauseAuth());
- mAltAuthInterceptor.showAlternateAuthBouncer(); // force show
+ mAlternateBouncer.showAlternateBouncer(); // force show
assertFalse(mController.shouldPauseAuth());
- assertTrue(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+ assertTrue(mAlternateBouncer.isShowingAlternateBouncer());
- mAltAuthInterceptor.hideAlternateAuthBouncer(); // stop force show
+ mAlternateBouncer.hideAlternateBouncer(); // stop force show
assertTrue(mController.shouldPauseAuth());
- assertFalse(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+ assertFalse(mAlternateBouncer.isShowingAlternateBouncer());
}
@Test
@@ -258,7 +261,7 @@
mController.onViewDetached();
// THEN remove alternate auth interceptor
- verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
+ verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAlternateBouncer);
}
@Test
@@ -268,14 +271,15 @@
captureAltAuthInterceptor();
// GIVEN udfps bouncer isn't showing
- mAltAuthInterceptor.hideAlternateAuthBouncer();
+ mAlternateBouncer.hideAlternateBouncer();
// WHEN touch is observed outside the view
mController.onTouchOutsideView();
// THEN bouncer / alt auth methods are never called
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
}
@Test
@@ -285,32 +289,33 @@
captureAltAuthInterceptor();
// GIVEN udfps bouncer is showing
- mAltAuthInterceptor.showAlternateAuthBouncer();
+ mAlternateBouncer.showAlternateBouncer();
// WHEN touch is observed outside the view 200ms later (just within threshold)
mSystemClock.advanceTime(200);
mController.onTouchOutsideView();
// THEN bouncer / alt auth methods are never called because not enough time has passed
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
}
@Test
- public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
+ public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showPrimaryBouncer() {
// GIVEN view is attached
mController.onViewAttached();
captureAltAuthInterceptor();
// GIVEN udfps bouncer is showing
- mAltAuthInterceptor.showAlternateAuthBouncer();
+ mAlternateBouncer.showAlternateBouncer();
// WHEN touch is observed outside the view 205ms later
mSystemClock.advanceTime(205);
mController.onTouchOutsideView();
// THEN show the bouncer
- verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(eq(true));
}
@Test
@@ -341,7 +346,7 @@
when(mResourceContext.getString(anyInt())).thenReturn("test string");
// WHEN status bar expansion is 0 but udfps bouncer is requested
- mAltAuthInterceptor.showAlternateAuthBouncer();
+ mAlternateBouncer.showAlternateBouncer();
// THEN alpha is 255
verify(mView).setUnpausedAlpha(255);
@@ -372,7 +377,7 @@
captureKeyguardStateControllerCallback();
captureAltAuthInterceptor();
updateStatusBarExpansion(1f, true);
- mAltAuthInterceptor.showAlternateAuthBouncer();
+ mAlternateBouncer.showAlternateBouncer();
reset(mView);
// WHEN we're transitioning to the full shade
@@ -420,4 +425,37 @@
verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
}
+
+ @Test
+ // TODO(b/259264861): Tracking Bug
+ public void testUdfpsExpandedOverlayOn() {
+ // GIVEN view is attached and useExpandedOverlay is true
+ mController = createUdfpsKeyguardViewController(false, true);
+ mController.onViewAttached();
+ captureKeyGuardViewManagerCallback();
+
+ // WHEN a touch is received
+ mKeyguardViewManagerCallback.onTouch(
+ MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+ // THEN udfpsController onTouch is not called
+ assertTrue(mView.mUseExpandedOverlay);
+ verify(mUdfpsController, never()).onTouch(any());
+ }
+
+ @Test
+ // TODO(b/259264861): Tracking Bug
+ public void testUdfpsExpandedOverlayOff() {
+ // GIVEN view is attached and useExpandedOverlay is false
+ mController.onViewAttached();
+ captureKeyGuardViewManagerCallback();
+
+ // WHEN a touch is received
+ mKeyguardViewManagerCallback.onTouch(
+ MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+ // THEN udfpsController onTouch is called
+ assertFalse(mView.mUseExpandedOverlay);
+ verify(mUdfpsController).onTouch(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 7b19768..517e27a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -25,8 +25,8 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.KeyguardBouncer
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -59,20 +59,23 @@
}
override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
- mBouncerInteractor =
- BouncerInteractor(
+ mPrimaryBouncerInteractor =
+ PrimaryBouncerInteractor(
keyguardBouncerRepository,
mock(BouncerView::class.java),
mock(Handler::class.java),
mKeyguardStateController,
mock(KeyguardSecurityModel::class.java),
- mock(BouncerCallbackInteractor::class.java),
+ mock(PrimaryBouncerCallbackInteractor::class.java),
mock(FalsingCollector::class.java),
mock(DismissCallbackRegistry::class.java),
mock(KeyguardBypassController::class.java),
mKeyguardUpdateMonitor
)
- return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+ return createUdfpsKeyguardViewController(
+ /* useModernBouncer */ true, /* useExpandedOverlay */
+ false
+ )
}
/** After migration, replaces LockIconViewControllerTest version */
@@ -86,7 +89,7 @@
// WHEN the bouncer expansion is VISIBLE
val job = mController.listenForBouncerExpansion(this)
- keyguardBouncerRepository.setVisible(true)
+ keyguardBouncerRepository.setPrimaryVisible(true)
keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
yield()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 2af0557..d159714 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -24,7 +24,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index f8579ff..0fadc13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -120,6 +120,7 @@
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+ mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index a872e4b..d6e621f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
@@ -171,7 +172,7 @@
mCallbacks.onShareButtonTapped();
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@@ -181,7 +182,7 @@
mCallbacks.onDismissButtonTapped();
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@@ -192,7 +193,7 @@
mCallbacks.onSwipeDismissInitiated(mAnimator);
mCallbacks.onDismissButtonTapped();
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
}
@@ -224,4 +225,16 @@
verify(mTimeoutHandler).resetTimeout();
}
+
+ @Test
+ public void test_logsUseLastClipSource() {
+ mOverlayController.setClipData(mSampleClipData, "first.package");
+ mCallbacks.onDismissButtonTapped();
+ mOverlayController.setClipData(mSampleClipData, "second.package");
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
new file mode 100644
index 0000000..3b6f7d1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -0,0 +1,118 @@
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.CustomIconCache
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsEditingActivityTest : SysuiTestCase() {
+ private val uiExecutor = FakeExecutor(FakeSystemClock())
+
+ @Mock lateinit var controller: ControlsControllerImpl
+
+ @Mock lateinit var userTracker: UserTracker
+
+ @Mock lateinit var customIconCache: CustomIconCache
+
+ @Mock lateinit var uiController: ControlsUiController
+
+ private lateinit var controlsEditingActivity: ControlsEditingActivity_Factory
+ private var latch: CountDownLatch = CountDownLatch(1)
+
+ @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+ @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+ @Rule
+ @JvmField
+ var activityRule =
+ ActivityTestRule(
+ object :
+ SingleActivityFactory<TestableControlsEditingActivity>(
+ TestableControlsEditingActivity::class.java
+ ) {
+ override fun create(intent: Intent?): TestableControlsEditingActivity {
+ return TestableControlsEditingActivity(
+ uiExecutor,
+ controller,
+ userTracker,
+ customIconCache,
+ uiController,
+ mockDispatcher,
+ latch
+ )
+ }
+ },
+ false,
+ false
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val intent = Intent()
+ intent.putExtra(ControlsEditingActivity.EXTRA_STRUCTURE, "TestTitle")
+ val cname = ComponentName("TestPackageName", "TestClassName")
+ intent.putExtra(Intent.EXTRA_COMPONENT_NAME, cname)
+ activityRule.launchActivity(intent)
+ }
+
+ @Test
+ fun testBackCallbackRegistrationAndUnregistration() {
+ // 1. ensure that launching the activity results in it registering a callback
+ verify(mockDispatcher)
+ .registerOnBackInvokedCallback(
+ ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ captureCallback.capture()
+ )
+ activityRule.finishActivity()
+ latch.await() // ensure activity is finished
+ // 2. ensure that when the activity is finished, it unregisters the same callback
+ verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+ }
+
+ public class TestableControlsEditingActivity(
+ private val executor: FakeExecutor,
+ private val controller: ControlsControllerImpl,
+ private val userTracker: UserTracker,
+ private val customIconCache: CustomIconCache,
+ private val uiController: ControlsUiController,
+ private val mockDispatcher: OnBackInvokedDispatcher,
+ private val latch: CountDownLatch
+ ) : ControlsEditingActivity(executor, controller, userTracker, customIconCache, uiController) {
+ override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+ return mockDispatcher
+ }
+
+ override fun onStop() {
+ super.onStop()
+ // ensures that test runner thread does not proceed until ui thread is done
+ latch.countDown()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
new file mode 100644
index 0000000..0f06de2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -0,0 +1,122 @@
+package com.android.systemui.controls.management
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsFavoritingActivityTest : SysuiTestCase() {
+ @Main private val executor: Executor = MoreExecutors.directExecutor()
+
+ @Mock lateinit var controller: ControlsControllerImpl
+
+ @Mock lateinit var listingController: ControlsListingController
+
+ @Mock lateinit var userTracker: UserTracker
+
+ @Mock lateinit var uiController: ControlsUiController
+
+ private lateinit var controlsFavoritingActivity: ControlsFavoritingActivity_Factory
+ private var latch: CountDownLatch = CountDownLatch(1)
+
+ @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+ @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+ @Rule
+ @JvmField
+ var activityRule =
+ ActivityTestRule(
+ object :
+ SingleActivityFactory<TestableControlsFavoritingActivity>(
+ TestableControlsFavoritingActivity::class.java
+ ) {
+ override fun create(intent: Intent?): TestableControlsFavoritingActivity {
+ return TestableControlsFavoritingActivity(
+ executor,
+ controller,
+ listingController,
+ userTracker,
+ uiController,
+ mockDispatcher,
+ latch
+ )
+ }
+ },
+ false,
+ false
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val intent = Intent()
+ intent.putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
+ activityRule.launchActivity(intent)
+ }
+
+ @Test
+ fun testBackCallbackRegistrationAndUnregistration() {
+ // 1. ensure that launching the activity results in it registering a callback
+ verify(mockDispatcher)
+ .registerOnBackInvokedCallback(
+ ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ captureCallback.capture()
+ )
+ activityRule.finishActivity()
+ latch.await() // ensure activity is finished
+ // 2. ensure that when the activity is finished, it unregisters the same callback
+ verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+ }
+
+ public class TestableControlsFavoritingActivity(
+ executor: Executor,
+ controller: ControlsControllerImpl,
+ listingController: ControlsListingController,
+ userTracker: UserTracker,
+ uiController: ControlsUiController,
+ private val mockDispatcher: OnBackInvokedDispatcher,
+ private val latch: CountDownLatch
+ ) :
+ ControlsFavoritingActivity(
+ executor,
+ controller,
+ listingController,
+ userTracker,
+ uiController
+ ) {
+ override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+ return mockDispatcher
+ }
+
+ override fun onStop() {
+ super.onStop()
+ // ensures that test runner thread does not proceed until ui thread is done
+ latch.countDown()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index db41d8d..98ff8d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -16,27 +16,43 @@
package com.android.systemui.controls.management
+import android.Manifest
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
+import android.os.Bundle
import android.os.UserHandle
+import android.service.controls.ControlsProviderService
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.USE_APP_PANELS
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
@@ -51,10 +67,8 @@
class ControlsListingControllerImplTest : SysuiTestCase() {
companion object {
- private const val TEST_LABEL = "TEST_LABEL"
- private const val TEST_PERMISSION = "permission"
- fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
- fun <T> any(): T = Mockito.any<T>()
+ private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
}
@Mock
@@ -63,15 +77,17 @@
private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
@Mock
private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
- @Mock
- private lateinit var serviceInfo: ServiceInfo
- @Mock
- private lateinit var serviceInfo2: ServiceInfo
@Mock(stubOnly = true)
private lateinit var userTracker: UserTracker
+ @Mock(stubOnly = true)
+ private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
- private var componentName = ComponentName("pkg1", "class1")
- private var componentName2 = ComponentName("pkg2", "class2")
+ private var componentName = ComponentName("pkg", "class1")
+ private var activityName = ComponentName("pkg", "activity")
private val executor = FakeExecutor(FakeSystemClock())
@@ -87,9 +103,15 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(serviceInfo.componentName).thenReturn(componentName)
- `when`(serviceInfo2.componentName).thenReturn(componentName2)
`when`(userTracker.userId).thenReturn(user)
+ `when`(userTracker.userContext).thenReturn(context)
+ // Return disabled by default
+ `when`(packageManager.getComponentEnabledSetting(any()))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
+ mContext.setMockPackageManager(packageManager)
+
+ // Return true by default, we'll test the false path
+ `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
@@ -97,7 +119,14 @@
}
}
- controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
+ controller = ControlsListingControllerImpl(
+ wrapper,
+ executor,
+ { mockSL },
+ userTracker,
+ dumpManager,
+ featureFlags
+ )
verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
}
@@ -123,9 +152,16 @@
Unit
}
`when`(mockServiceListing.reload()).then {
- callback?.onServicesReloaded(listOf(serviceInfo))
+ callback?.onServicesReloaded(listOf(ServiceInfo(componentName)))
}
- ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker)
+ ControlsListingControllerImpl(
+ mContext,
+ exec,
+ { mockServiceListing },
+ userTracker,
+ dumpManager,
+ featureFlags
+ )
}
@Test
@@ -148,7 +184,7 @@
@Test
fun testCallbackGetsList() {
- val list = listOf(serviceInfo)
+ val list = listOf(ServiceInfo(componentName))
controller.addCallback(mockCallback)
controller.addCallback(mockCallbackOther)
@@ -188,6 +224,8 @@
@Test
fun testChangeUserSendsCorrectServiceUpdate() {
+ val serviceInfo = ServiceInfo(componentName)
+
val list = listOf(serviceInfo)
controller.addCallback(mockCallback)
@@ -223,4 +261,297 @@
verify(mockCallback).onServicesUpdated(capture(captor))
assertEquals(0, captor.value.size)
}
+
+ @Test
+ fun test_nullPanelActivity() {
+ val list = listOf(ServiceInfo(componentName))
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testNoActivity_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityWithoutPermission_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ setUpQueryResult(listOf(ActivityInfo(activityName)))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityPermissionNotExported_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ setUpQueryResult(listOf(
+ ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS)
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDisabled_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityEnabled_correctPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDefaultEnabled_correctPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = true,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDefaultDisabled_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = false,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDefaultEnabled_flagDisabled_nullPanel() {
+ `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false)
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName,
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = true,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDifferentPackage_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ ComponentName("other_package", "cls")
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = true,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testListingsNotModifiedByCallback() {
+ // This test checks that if the list passed to the callback is modified, it has no effect
+ // in the resulting services
+ val list = mutableListOf<ServiceInfo>()
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ list.add(ServiceInfo(ComponentName("a", "b")))
+ executor.runAllReady()
+
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
+ private fun ServiceInfo(
+ componentName: ComponentName,
+ panelActivityComponentName: ComponentName? = null
+ ): ServiceInfo {
+ return ServiceInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ panelActivityComponentName?.let {
+ metaData = Bundle().apply {
+ putString(
+ ControlsProviderService.META_DATA_PANEL_ACTIVITY,
+ it.flattenToShortString()
+ )
+ }
+ }
+ }
+ }
+
+ private fun ActivityInfo(
+ componentName: ComponentName,
+ exported: Boolean = false,
+ enabled: Boolean = true,
+ permission: String? = null
+ ): ActivityInfo {
+ return ActivityInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ this.permission = permission
+ this.exported = exported
+ this.enabled = enabled
+ }
+ }
+
+ private fun setUpQueryResult(infos: List<ActivityInfo>) {
+ `when`(
+ packageManager.queryIntentActivitiesAsUser(
+ argThat(IntentMatcher(activityName)),
+ argThat(FlagsMatcher(FLAGS)),
+ eq(UserHandle.of(user))
+ )
+ ).thenReturn(infos.map {
+ ResolveInfo().apply { activityInfo = it }
+ })
+ }
+
+ private class IntentMatcher(
+ private val componentName: ComponentName
+ ) : ArgumentMatcher<Intent> {
+ override fun matches(argument: Intent?): Boolean {
+ return argument?.component == componentName
+ }
+ }
+
+ private class FlagsMatcher(
+ private val flags: Long
+ ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
+ override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean {
+ return flags == argument?.value
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
new file mode 100644
index 0000000..56c3efe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.controls.management
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsProviderSelectorActivityTest : SysuiTestCase() {
+ @Main private val executor: Executor = MoreExecutors.directExecutor()
+
+ @Background private val backExecutor: Executor = MoreExecutors.directExecutor()
+
+ @Mock lateinit var listingController: ControlsListingController
+
+ @Mock lateinit var controlsController: ControlsController
+
+ @Mock lateinit var userTracker: UserTracker
+
+ @Mock lateinit var uiController: ControlsUiController
+
+ private lateinit var controlsProviderSelectorActivity: ControlsProviderSelectorActivity_Factory
+ private var latch: CountDownLatch = CountDownLatch(1)
+
+ @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+ @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+ @Rule
+ @JvmField
+ var activityRule =
+ ActivityTestRule(
+ object :
+ SingleActivityFactory<TestableControlsProviderSelectorActivity>(
+ TestableControlsProviderSelectorActivity::class.java
+ ) {
+ override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
+ return TestableControlsProviderSelectorActivity(
+ executor,
+ backExecutor,
+ listingController,
+ controlsController,
+ userTracker,
+ uiController,
+ mockDispatcher,
+ latch
+ )
+ }
+ },
+ false,
+ false
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val intent = Intent()
+ intent.putExtra(ControlsProviderSelectorActivity.BACK_SHOULD_EXIT, true)
+ activityRule.launchActivity(intent)
+ }
+
+ @Test
+ fun testBackCallbackRegistrationAndUnregistration() {
+ // 1. ensure that launching the activity results in it registering a callback
+ verify(mockDispatcher)
+ .registerOnBackInvokedCallback(
+ ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ captureCallback.capture()
+ )
+ activityRule.finishActivity()
+ latch.await() // ensure activity is finished
+ // 2. ensure that when the activity is finished, it unregisters the same callback
+ verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+ }
+
+ public class TestableControlsProviderSelectorActivity(
+ executor: Executor,
+ backExecutor: Executor,
+ listingController: ControlsListingController,
+ controlsController: ControlsController,
+ userTracker: UserTracker,
+ uiController: ControlsUiController,
+ private val mockDispatcher: OnBackInvokedDispatcher,
+ private val latch: CountDownLatch
+ ) :
+ ControlsProviderSelectorActivity(
+ executor,
+ backExecutor,
+ listingController,
+ controlsController,
+ userTracker,
+ uiController
+ ) {
+ override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+ return mockDispatcher
+ }
+
+ override fun onStop() {
+ super.onStop()
+ // ensures that test runner thread does not proceed until ui thread is done
+ latch.countDown()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index efb3db7..314b176 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import org.junit.After
@@ -46,9 +47,10 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
@MediumTest
@RunWith(AndroidTestingRunner::class)
@@ -67,6 +69,10 @@
private lateinit var controller: ControlsController
@Mock
+ private lateinit var mainExecutor: Executor
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
private lateinit var listingController: ControlsListingController
@Mock
private lateinit var iIntentSender: IIntentSender
@@ -81,8 +87,9 @@
) {
override fun create(intent: Intent?): TestControlsRequestDialog {
return TestControlsRequestDialog(
+ mainExecutor,
controller,
- fakeBroadcastDispatcher,
+ userTracker,
listingController
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
index 3f6308b..ec239f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
@@ -16,11 +16,13 @@
package com.android.systemui.controls.management
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
class TestControlsRequestDialog(
+ mainExecutor: Executor,
controller: ControlsController,
- dispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
listingController: ControlsListingController
-) : ControlsRequestDialog(controller, dispatcher, listingController)
\ No newline at end of file
+) : ControlsRequestDialog(mainExecutor, controller, userTracker, listingController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 2f206ad..07d7e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeSensors.TriggerSensor;
import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +100,8 @@
@Mock
private DevicePostureController mDevicePostureController;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private ProximitySensor mProximitySensor;
// Capture listeners so that they can be used to send events
@@ -428,7 +431,7 @@
DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
assertFalse(sensor.mIgnoresSetting);
@@ -440,7 +443,7 @@
super(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
for (TriggerSensor sensor : mTriggerSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6091d3a..82432ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -98,6 +99,8 @@
@Mock
private DevicePostureController mDevicePostureController;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private SessionTracker mSessionTracker;
private DozeTriggers mTriggers;
@@ -131,7 +134,7 @@
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
new file mode 100644
index 0000000..99406ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.statusbar.BlurUtils
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
+
+ companion object {
+ private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
+ private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
+ private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
+ private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
+ private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
+ private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
+ private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
+ private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
+ private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
+ private const val DREAM_OUT_ALPHA_DURATION = 10L
+ private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
+ private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
+ private const val DREAM_OUT_BLUR_DURATION = 13L
+ }
+
+ @Mock private lateinit var mockAnimator: AnimatorSet
+ @Mock private lateinit var blurUtils: BlurUtils
+ @Mock private lateinit var hostViewController: ComplicationHostViewController
+ @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
+ @Mock private lateinit var stateController: DreamOverlayStateController
+ private lateinit var controller: DreamOverlayAnimationsController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ controller =
+ DreamOverlayAnimationsController(
+ blurUtils,
+ hostViewController,
+ statusBarViewController,
+ stateController,
+ DREAM_IN_BLUR_ANIMATION_DURATION,
+ DREAM_IN_BLUR_ANIMATION_DELAY,
+ DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
+ DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_OUT_TRANSLATION_Y_DISTANCE,
+ DREAM_OUT_TRANSLATION_Y_DURATION,
+ DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
+ DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
+ DREAM_OUT_ALPHA_DURATION,
+ DREAM_OUT_ALPHA_DELAY_BOTTOM,
+ DREAM_OUT_ALPHA_DELAY_TOP,
+ DREAM_OUT_BLUR_DURATION
+ )
+ }
+
+ @Test
+ fun testExitAnimationOnEnd() {
+ val mockCallback: () -> Unit = mock()
+
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mockCallback,
+ animatorBuilder = { mockAnimator }
+ )
+
+ val captor = argumentCaptor<Animator.AnimatorListener>()
+ verify(mockAnimator).addListener(captor.capture())
+ val listener = captor.value
+
+ verify(mockCallback, never()).invoke()
+ listener.onAnimationEnd(mockAnimator)
+ verify(mockCallback, times(1)).invoke()
+ }
+
+ @Test
+ fun testCancellation() {
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mock(),
+ animatorBuilder = { mockAnimator }
+ )
+
+ verify(mockAnimator, never()).cancel()
+ controller.cancelAnimations()
+ verify(mockAnimator, times(1)).cancel()
+ }
+
+ @Test
+ fun testExitAfterStartWillCancel() {
+ val mockStartAnimator: AnimatorSet = mock()
+ val mockExitAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
+
+ verify(mockStartAnimator, never()).cancel()
+
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mock(),
+ animatorBuilder = { mockExitAnimator }
+ )
+
+ // Verify that we cancelled the start animator in favor of the exit
+ // animator.
+ verify(mockStartAnimator, times(1)).cancel()
+ verify(mockExitAnimator, never()).cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c234178..73c226d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -36,10 +36,10 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import org.junit.Before;
@@ -90,7 +90,7 @@
ViewRootImpl mViewRoot;
@Mock
- BouncerCallbackInteractor mBouncerCallbackInteractor;
+ PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock
DreamOverlayAnimationsController mAnimationsController;
@@ -106,7 +106,7 @@
when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
- when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(mBouncer);
+ when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(mBouncer);
when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
mController = new DreamOverlayContainerViewController(
@@ -121,7 +121,7 @@
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL,
MILLIS_UNTIL_FULL_JITTER,
- mBouncerCallbackInteractor,
+ mPrimaryBouncerCallbackInteractor,
mAnimationsController,
mStateController);
}
@@ -167,8 +167,8 @@
@Test
public void testBouncerAnimation_doesNotApply() {
- final ArgumentCaptor<BouncerExpansionCallback> bouncerExpansionCaptor =
- ArgumentCaptor.forClass(BouncerExpansionCallback.class);
+ final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
+ ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
mController.onViewAttached();
verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
@@ -178,8 +178,8 @@
@Test
public void testBouncerAnimation_updateBlur() {
- final ArgumentCaptor<BouncerExpansionCallback> bouncerExpansionCaptor =
- ArgumentCaptor.forClass(BouncerExpansionCallback.class);
+ final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
+ ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
mController.onViewAttached();
verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
@@ -204,7 +204,7 @@
mController.onViewAttached();
verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
- verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+ verify(mAnimationsController, never()).cancelAnimations();
}
@Test
@@ -221,6 +221,6 @@
mController.onViewAttached();
mController.onViewDetached();
- verify(mAnimationsController).cancelRunningEntryAnimations();
+ verify(mAnimationsController).cancelAnimations();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f04a37f..ffb8342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -337,4 +338,28 @@
verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
}
+
+ @Test
+ public void testWakeUp() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ true /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ final Runnable callback = mock(Runnable.class);
+ mService.onWakeUp(callback);
+ mMainExecutor.runAllReady();
+ verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+ }
+
+ @Test
+ public void testWakeUpBeforeStartDoesNothing() {
+ final Runnable callback = mock(Runnable.class);
+ mService.onWakeUp(callback);
+ mMainExecutor.runAllReady();
+ verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 849ac5e..7a2ba95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -347,21 +347,22 @@
addComplication(engine, thirdViewInfo);
- // The first added view should now be underneath the second view.
+ // The first added view should now be underneath the third view.
verifyChange(firstViewInfo, false, lp -> {
assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.topMargin).isEqualTo(margin);
});
- // The second view should be in underneath the third view.
+ // The second view should be to the start of the third view.
verifyChange(secondViewInfo, false, lp -> {
assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.getMarginEnd()).isEqualTo(margin);
});
- // The third view should be in at the top.
+ // The third view should be at the top end corner. No margin should be applied if not
+ // specified.
verifyChange(thirdViewInfo, true, lp -> {
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
@@ -425,14 +426,14 @@
addComplication(engine, thirdViewInfo);
- // The first added view should now be underneath the second view.
+ // The first added view should now be underneath the third view.
verifyChange(firstViewInfo, false, lp -> {
assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.topMargin).isEqualTo(complicationMargin);
});
- // The second view should be in underneath the third view.
+ // The second view should be to the start of the third view.
verifyChange(secondViewInfo, false, lp -> {
assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
@@ -441,6 +442,69 @@
}
/**
+ * Ensures the root complication applies margin if specified.
+ */
+ @Test
+ public void testRootComplicationSpecifiedMargin() {
+ final int defaultMargin = 5;
+ final int complicationMargin = 10;
+ final ComplicationLayoutEngine engine =
+ new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0);
+
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, firstViewInfo);
+
+ final ViewInfo secondViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 0),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, secondViewInfo);
+
+ firstViewInfo.clearInvocations();
+ secondViewInfo.clearInvocations();
+
+ final ViewInfo thirdViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 1,
+ complicationMargin),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, thirdViewInfo);
+
+ // The third view is the root view and has specified margin, which should be applied based
+ // on its direction.
+ verifyChange(thirdViewInfo, true, lp -> {
+ assertThat(lp.getMarginStart()).isEqualTo(0);
+ assertThat(lp.getMarginEnd()).isEqualTo(complicationMargin);
+ assertThat(lp.topMargin).isEqualTo(0);
+ assertThat(lp.bottomMargin).isEqualTo(0);
+ });
+ }
+
+ /**
* Ensures layout in a particular position updates.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index cb7e47b..ce7561e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -97,6 +97,31 @@
}
/**
+ * Ensures ComplicationLayoutParams correctly returns whether the complication specified margin.
+ */
+ @Test
+ public void testIsMarginSpecified() {
+ final ComplicationLayoutParams paramsNoMargin = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0);
+ assertThat(paramsNoMargin.isMarginSpecified()).isFalse();
+
+ final ComplicationLayoutParams paramsWithMargin = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ 20 /*margin*/);
+ assertThat(paramsWithMargin.isMarginSpecified()).isTrue();
+ }
+
+ /**
* Ensures unspecified margin uses default.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index aa8c93e..30ad485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -90,7 +90,10 @@
private ActivityStarter mActivityStarter;
@Mock
- UiEventLogger mUiEventLogger;
+ private UiEventLogger mUiEventLogger;
+
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
@Before
public void setup() {
@@ -164,6 +167,29 @@
verify(mDreamOverlayStateController).addComplication(mComplication);
}
+ @Test
+ public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setServiceAvailable(true);
+ setHaveFavorites(false);
+
+ // Complication not available on start.
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+
+ // Favorite controls added, complication should be available now.
+ setHaveFavorites(true);
+
+ // Dream overlay becomes active.
+ setDreamOverlayActive(true);
+
+ // Verify complication is added.
+ verify(mDreamOverlayStateController).addComplication(mComplication);
+ }
+
/**
* Ensures clicking home controls chip logs UiEvent.
*/
@@ -196,10 +222,17 @@
private void setServiceAvailable(boolean value) {
final List<ControlsServiceInfo> serviceInfos = mock(List.class);
+ when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
when(serviceInfos.isEmpty()).thenReturn(!value);
triggerControlsListingCallback(serviceInfos);
}
+ private void setDreamOverlayActive(boolean value) {
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
+ verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
+ mStateCallbackCaptor.getValue().onStateChanged();
+ }
+
private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) {
verify(mControlsListingController).addCallback(mCallbackCaptor.capture());
mCallbackCaptor.getValue().onServicesUpdated(serviceInfos);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
index 14a5702..4e3aca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.dreams.touch;
-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.never;
@@ -33,6 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -52,6 +51,7 @@
@RunWith(AndroidTestingRunner.class)
public class HideComplicationTouchHandlerTest extends SysuiTestCase {
private static final int RESTORE_TIMEOUT = 1000;
+ private static final int HIDE_DELAY = 500;
@Mock
Complication.VisibilityController mVisibilityController;
@@ -71,11 +71,18 @@
@Mock
DreamTouchHandler.TouchSession mSession;
- FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock
+ DreamOverlayStateController mStateController;
+
+ FakeSystemClock mClock;
+
+ FakeExecutor mFakeExecutor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mClock = new FakeSystemClock();
+ mFakeExecutor = new FakeExecutor(mClock);
}
/**
@@ -86,10 +93,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report multiple active sessions.
when(mSession.getActiveSessionCount()).thenReturn(2);
@@ -103,8 +111,10 @@
// Verify session end.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -115,10 +125,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session.
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -132,8 +143,10 @@
// Verify session end.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -144,10 +157,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -177,8 +191,10 @@
// Verify session ended.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -189,10 +205,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -221,11 +238,11 @@
inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent);
mFakeExecutor.runAllReady();
- // Verify callback to restore visibility cancelled.
- verify(mHandler).removeCallbacks(any());
-
+ // Verify visibility controller doesn't hide until after timeout
+ verify(mVisibilityController, never()).setVisibility(eq(View.INVISIBLE));
+ mClock.advanceTime(HIDE_DELAY);
// Verify visibility controller told to hide complications.
- verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean());
+ verify(mVisibilityController).setVisibility(eq(View.INVISIBLE));
Mockito.clearInvocations(mVisibilityController, mHandler);
@@ -235,11 +252,8 @@
mFakeExecutor.runAllReady();
// Verify visibility controller told to show complications.
- ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
- verify(mHandler).postDelayed(delayRunnableCaptor.capture(),
- eq(Long.valueOf(RESTORE_TIMEOUT)));
- delayRunnableCaptor.getValue().run();
- verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean());
+ mClock.advanceTime(RESTORE_TIMEOUT);
+ verify(mVisibilityController).setVisibility(eq(View.VISIBLE));
// Verify session ended.
verify(mSession).pop();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 318f2bc..170a70f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -20,7 +20,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
-import java.lang.IllegalStateException
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
@@ -29,12 +28,12 @@
@RunWith(AndroidTestingRunner::class)
class FakeFeatureFlagsTest : SysuiTestCase() {
- private val unreleasedFlag = UnreleasedFlag(-1000)
- private val releasedFlag = ReleasedFlag(-1001)
- private val stringFlag = StringFlag(-1002)
- private val resourceBooleanFlag = ResourceBooleanFlag(-1003, resourceId = -1)
- private val resourceStringFlag = ResourceStringFlag(-1004, resourceId = -1)
- private val sysPropBooleanFlag = SysPropBooleanFlag(-1005, name = "test")
+ private val unreleasedFlag = UnreleasedFlag(-1000, "-1000", "test")
+ private val releasedFlag = ReleasedFlag(-1001, "-1001", "test")
+ private val stringFlag = StringFlag(-1002, "-1002", "test")
+ private val resourceBooleanFlag = ResourceBooleanFlag(-1003, "-1003", "test", resourceId = -1)
+ private val resourceStringFlag = ResourceStringFlag(-1004, "-1004", "test", resourceId = -1)
+ private val sysPropBooleanFlag = SysPropBooleanFlag(-1005, "test", "test")
/**
* FakeFeatureFlags does not honor any default values. All flags which are accessed must be
@@ -47,7 +46,7 @@
assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
fail("Expected an exception when accessing an unspecified flag.")
} catch (ex: IllegalStateException) {
- assertThat(ex.message).contains("TEAMFOOD")
+ assertThat(ex.message).contains("id=1")
}
try {
assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
new file mode 100644
index 0000000..1e7b1f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+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()!
+ */
+@SmallTest
+class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsDebugRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+ }
+
+ @Test
+ fun testRestart_ImmediateWhenAsleep() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ restarter.restart()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_WaitsForSceenOff() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ captor.value.onFinishedGoingToSleep()
+
+ verify(systemExitRestarter).restart()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 9c22cd2..7592cc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -31,10 +31,6 @@
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.Serializable
-import java.io.StringWriter
-import java.util.function.Consumer
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -46,8 +42,12 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.Serializable
+import java.io.StringWriter
+import java.util.function.Consumer
+import org.mockito.Mockito.`when` as whenever
/**
* NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
@@ -57,21 +57,32 @@
class FeatureFlagsDebugTest : SysuiTestCase() {
private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug
- @Mock private lateinit var flagManager: FlagManager
- @Mock private lateinit var mockContext: Context
- @Mock private lateinit var secureSettings: SecureSettings
- @Mock private lateinit var systemProperties: SystemPropertiesHelper
- @Mock private lateinit var resources: Resources
- @Mock private lateinit var commandRegistry: CommandRegistry
- @Mock private lateinit var restarter: Restarter
+ @Mock
+ private lateinit var flagManager: FlagManager
+ @Mock
+ private lateinit var mockContext: Context
+ @Mock
+ private lateinit var secureSettings: SecureSettings
+ @Mock
+ private lateinit var systemProperties: SystemPropertiesHelper
+ @Mock
+ private lateinit var resources: Resources
+ @Mock
+ private lateinit var commandRegistry: CommandRegistry
+ @Mock
+ private lateinit var restarter: Restarter
private val flagMap = mutableMapOf<Int, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<Int>
private val serverFlagReader = ServerFlagReaderFake()
private val deviceConfig = DeviceConfigProxyFake()
- private val teamfoodableFlagA = UnreleasedFlag(500, true)
- private val teamfoodableFlagB = ReleasedFlag(501, true)
+ private val teamfoodableFlagA = UnreleasedFlag(
+ 500, name = "a", namespace = "test", teamfood = true
+ )
+ private val teamfoodableFlagB = ReleasedFlag(
+ 501, name = "b", namespace = "test", teamfood = true
+ )
@Before
fun setup() {
@@ -84,7 +95,6 @@
secureSettings,
systemProperties,
resources,
- deviceConfig,
serverFlagReader,
flagMap,
restarter
@@ -92,8 +102,10 @@
mFeatureFlagsDebug.init()
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
- verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
- any())
+ verify(mockContext).registerReceiver(
+ capture(), any(), nullable(), nullable(),
+ any()
+ )
}
clearCacheAction = withArgCaptor {
verify(flagManager).clearCacheAction = capture()
@@ -107,10 +119,42 @@
whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
- assertThat(mFeatureFlagsDebug.isEnabled(ReleasedFlag(2))).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(UnreleasedFlag(3))).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(ReleasedFlag(4))).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(UnreleasedFlag(5))).isFalse()
+ assertThat(
+ mFeatureFlagsDebug.isEnabled(
+ ReleasedFlag(
+ 2,
+ name = "2",
+ namespace = "test"
+ )
+ )
+ ).isTrue()
+ assertThat(
+ mFeatureFlagsDebug.isEnabled(
+ UnreleasedFlag(
+ 3,
+ name = "3",
+ namespace = "test"
+ )
+ )
+ ).isTrue()
+ assertThat(
+ mFeatureFlagsDebug.isEnabled(
+ ReleasedFlag(
+ 4,
+ name = "3",
+ namespace = "test"
+ )
+ )
+ ).isFalse()
+ assertThat(
+ mFeatureFlagsDebug.isEnabled(
+ UnreleasedFlag(
+ 5,
+ name = "4",
+ namespace = "test"
+ )
+ )
+ ).isFalse()
}
@Test
@@ -138,9 +182,9 @@
@Test
fun teamFoodFlag_Overridden() {
whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any()))
- .thenReturn(true)
+ .thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any()))
- .thenReturn(false)
+ .thenReturn(false)
whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
@@ -161,17 +205,26 @@
whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false)
- assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(1, 1001))).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, 1002))).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, 1003))).isTrue()
+ assertThat(
+ mFeatureFlagsDebug.isEnabled(
+ ResourceBooleanFlag(
+ 1,
+ "1",
+ "test",
+ 1001
+ )
+ )
+ ).isFalse()
+ assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
+ assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, 1004))
+ mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, 1005))
+ mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
}
}
@@ -184,36 +237,30 @@
return@thenAnswer it.getArgument(1)
}
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a"))).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b"))).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", true))).isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(4, "d", false))).isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e"))).isFalse()
- }
-
- @Test
- fun readDeviceConfigBooleanFlag() {
- val namespace = "test_namespace"
- deviceConfig.setProperty(namespace, "a", "true", false)
- deviceConfig.setProperty(namespace, "b", "false", false)
- deviceConfig.setProperty(namespace, "c", null, false)
-
- assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
- .isTrue()
- assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
- .isFalse()
- assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
- .isFalse()
+ assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
+ assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
+ assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
+ assertThat(
+ mFeatureFlagsDebug.isEnabled(
+ SysPropBooleanFlag(
+ 4,
+ "d",
+ "test",
+ false
+ )
+ )
+ ).isFalse()
+ assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
}
@Test
fun readStringFlag() {
whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "biz"))).isEqualTo("biz")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "baz"))).isEqualTo("baz")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "buz"))).isEqualTo("foo")
- assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "buz"))).isEqualTo("bar")
+ assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
+ assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
+ assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
+ assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
}
@Test
@@ -229,20 +276,47 @@
whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6")
- assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(1, 1001))).isEqualTo("")
- assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(2, 1002))).isEqualTo("resource2")
- assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(3, 1003))).isEqualTo("override3")
+ assertThat(
+ mFeatureFlagsDebug.getString(
+ ResourceStringFlag(
+ 1,
+ "1",
+ "test",
+ 1001
+ )
+ )
+ ).isEqualTo("")
+ assertThat(
+ mFeatureFlagsDebug.getString(
+ ResourceStringFlag(
+ 2,
+ "2",
+ "test",
+ 1002
+ )
+ )
+ ).isEqualTo("resource2")
+ assertThat(
+ mFeatureFlagsDebug.getString(
+ ResourceStringFlag(
+ 3,
+ "3",
+ "test",
+ 1003
+ )
+ )
+ ).isEqualTo("override3")
Assert.assertThrows(NullPointerException::class.java) {
- mFeatureFlagsDebug.getString(ResourceStringFlag(4, 1004))
+ mFeatureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
}
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.getString(ResourceStringFlag(5, 1005))
+ mFeatureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsDebug.getString(ResourceStringFlag(6, 1005))
+ mFeatureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
}
}
@@ -250,10 +324,10 @@
fun readIntFlag() {
whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22)
whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, 12))).isEqualTo(12)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, 93))).isEqualTo(93)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, 8))).isEqualTo(22)
- assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, 234))).isEqualTo(48)
+ assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
+ assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
+ assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
+ assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
}
@Test
@@ -269,26 +343,26 @@
whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
- assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, 1001))).isEqualTo(88)
- assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, 1002))).isEqualTo(61)
- assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, 1003))).isEqualTo(20)
+ assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
+ assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
+ assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
Assert.assertThrows(NotFoundException::class.java) {
- mFeatureFlagsDebug.getInt(ResourceIntFlag(4, 1004))
+ mFeatureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NotFoundException::class.java) {
- mFeatureFlagsDebug.getInt(ResourceIntFlag(5, 1005))
+ mFeatureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
}
}
@Test
fun broadcastReceiver_IgnoresInvalidData() {
- addFlag(UnreleasedFlag(1))
- addFlag(ResourceBooleanFlag(2, 1002))
- addFlag(StringFlag(3, "flag3"))
- addFlag(ResourceStringFlag(4, 1004))
+ addFlag(UnreleasedFlag(1, "1", "test"))
+ addFlag(ResourceBooleanFlag(2, "2", "test", 1002))
+ addFlag(StringFlag(3, "3", "test", "flag3"))
+ addFlag(ResourceStringFlag(4, "4", "test", 1004))
broadcastReceiver.onReceive(mockContext, null)
broadcastReceiver.onReceive(mockContext, Intent())
@@ -304,7 +378,7 @@
@Test
fun intentWithId_NoValueKeyClears() {
- addFlag(UnreleasedFlag(1))
+ addFlag(UnreleasedFlag(1, name = "1", namespace = "test"))
// trying to erase an id not in the map does nothing
broadcastReceiver.onReceive(
@@ -323,10 +397,10 @@
@Test
fun setBooleanFlag() {
- addFlag(UnreleasedFlag(1))
- addFlag(UnreleasedFlag(2))
- addFlag(ResourceBooleanFlag(3, 1003))
- addFlag(ResourceBooleanFlag(4, 1004))
+ addFlag(UnreleasedFlag(1, "1", "test"))
+ addFlag(UnreleasedFlag(2, "2", "test"))
+ addFlag(ResourceBooleanFlag(3, "3", "test", 1003))
+ addFlag(ResourceBooleanFlag(4, "4", "test", 1004))
setByBroadcast(1, false)
verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}")
@@ -343,8 +417,8 @@
@Test
fun setStringFlag() {
- addFlag(StringFlag(1, "flag1"))
- addFlag(ResourceStringFlag(2, 1002))
+ addFlag(StringFlag(1, "flag1", "1", "test"))
+ addFlag(ResourceStringFlag(2, "2", "test", 1002))
setByBroadcast(1, "override1")
verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}")
@@ -355,7 +429,7 @@
@Test
fun setFlag_ClearsCache() {
- val flag1 = addFlag(StringFlag(1, "flag1"))
+ val flag1 = addFlag(StringFlag(1, "1", "test", "flag1"))
whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("original")
// gets the flag & cache it
@@ -377,31 +451,31 @@
@Test
fun serverSide_Overrides_MakesFalse() {
- val flag = ReleasedFlag(100)
+ val flag = ReleasedFlag(100, "100", "test")
- serverFlagReader.setFlagValue(flag.id, false)
+ serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
assertThat(mFeatureFlagsDebug.isEnabled(flag)).isFalse()
}
@Test
fun serverSide_Overrides_MakesTrue() {
- val flag = UnreleasedFlag(100)
+ val flag = UnreleasedFlag(100, name = "100", namespace = "test")
- serverFlagReader.setFlagValue(flag.id, true)
+ serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
}
@Test
fun dumpFormat() {
- val flag1 = ReleasedFlag(1)
- val flag2 = ResourceBooleanFlag(2, 1002)
- val flag3 = UnreleasedFlag(3)
- val flag4 = StringFlag(4, "")
- val flag5 = StringFlag(5, "flag5default")
- val flag6 = ResourceStringFlag(6, 1006)
- val flag7 = ResourceStringFlag(7, 1007)
+ val flag1 = ReleasedFlag(1, "1", "test")
+ val flag2 = ResourceBooleanFlag(2, "2", "test", 1002)
+ val flag3 = UnreleasedFlag(3, "3", "test")
+ val flag4 = StringFlag(4, "4", "test", "")
+ val flag5 = StringFlag(5, "5", "test", "flag5default")
+ val flag6 = ResourceStringFlag(6, "6", "test", 1006)
+ val flag7 = ResourceStringFlag(7, "7", "test", 1007)
whenever(resources.getBoolean(1002)).thenReturn(true)
whenever(resources.getString(1006)).thenReturn("resource1006")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
new file mode 100644
index 0000000..68ca48d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+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()!
+ */
+@SmallTest
+class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsReleaseRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter =
+ FeatureFlagsReleaseRestarter(
+ wakefulnessLifecycle,
+ batteryController,
+ executor,
+ systemExitRestarter
+ )
+ }
+
+ @Test
+ fun testRestart_ScheduledWhenReady() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testRestart_RestartsWhenIdle() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenAwake() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenNotPluggedIn() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotDoubleSheduled() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testWakefulnessLifecycle_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+ captor.value.onFinishedGoingToSleep()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testBatteryController_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ captor.value.onBatteryLevelChanged(0, true, true)
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index b2dd60c..d5b5a4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -25,8 +25,8 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
/**
* NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
@@ -59,7 +59,9 @@
fun testBooleanResourceFlag() {
val flagId = 213
val flagResourceId = 3
- val flag = ResourceBooleanFlag(flagId, flagResourceId)
+ val flagName = "213"
+ val flagNamespace = "test"
+ val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId)
whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue()
}
@@ -71,57 +73,45 @@
whenever(mResources.getString(1003)).thenReturn(null)
whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
- assertThat(mFeatureFlagsRelease.getString(ResourceStringFlag(1, 1001))).isEqualTo("")
- assertThat(mFeatureFlagsRelease.getString(ResourceStringFlag(2, 1002))).isEqualTo("res2")
+ assertThat(mFeatureFlagsRelease.getString(
+ ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("")
+ assertThat(mFeatureFlagsRelease.getString(
+ ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2")
assertThrows(NullPointerException::class.java) {
- mFeatureFlagsRelease.getString(ResourceStringFlag(3, 1003))
+ mFeatureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
}
assertThrows(NameNotFoundException::class.java) {
- mFeatureFlagsRelease.getString(ResourceStringFlag(4, 1004))
+ mFeatureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
}
}
@Test
- fun testReadDeviceConfigBooleanFlag() {
- val namespace = "test_namespace"
- deviceConfig.setProperty(namespace, "a", "true", false)
- deviceConfig.setProperty(namespace, "b", "false", false)
- deviceConfig.setProperty(namespace, "c", null, false)
-
- assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
- .isTrue()
- assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
- .isFalse()
- assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
- .isFalse()
- }
-
- @Test
fun testSysPropBooleanFlag() {
val flagId = 213
val flagName = "sys_prop_flag"
+ val flagNamespace = "test"
val flagDefault = true
- val flag = SysPropBooleanFlag(flagId, flagName, flagDefault)
+ val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault)
whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
assertThat(mFeatureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
}
@Test
fun serverSide_OverridesReleased_MakesFalse() {
- val flag = ReleasedFlag(100)
+ val flag = ReleasedFlag(100, "100", "test")
- serverFlagReader.setFlagValue(flag.id, false)
+ serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
}
@Test
fun serverSide_OverridesUnreleased_Ignored() {
- val flag = UnreleasedFlag(100)
+ val flag = UnreleasedFlag(100, "100", "test")
- serverFlagReader.setFlagValue(flag.id, true)
+ serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 7355319..fea91c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -33,10 +33,10 @@
@Mock private lateinit var featureFlags: FeatureFlagsDebug
@Mock private lateinit var pw: PrintWriter
private val flagMap = mutableMapOf<Int, Flag<*>>()
- private val flagA = UnreleasedFlag(500)
- private val flagB = ReleasedFlag(501)
- private val stringFlag = StringFlag(502, "abracadabra")
- private val intFlag = IntFlag(503, 12)
+ private val flagA = UnreleasedFlag(500, "500", "test")
+ private val flagB = ReleasedFlag(501, "501", "test")
+ private val stringFlag = StringFlag(502, "502", "test", "abracadabra")
+ private val intFlag = IntFlag(503, "503", "test", 12)
private lateinit var cmd: FlagCommand
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index 17324a0..fca7e96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -64,14 +64,14 @@
verifyNoMoreInteractions(mFlagSettingsHelper)
// adding the first listener registers the observer
- mFlagManager.addListener(ReleasedFlag(1), listener1)
+ mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
val observer = withArgCaptor<ContentObserver> {
verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
}
verifyNoMoreInteractions(mFlagSettingsHelper)
// adding another listener does nothing
- mFlagManager.addListener(ReleasedFlag(2), listener2)
+ mFlagManager.addListener(ReleasedFlag(2, "2", "test"), listener2)
verifyNoMoreInteractions(mFlagSettingsHelper)
// removing the original listener does nothing with second one still present
@@ -89,7 +89,7 @@
val listener = mock<FlagListenable.Listener>()
val clearCacheAction = mock<Consumer<Int>>()
mFlagManager.clearCacheAction = clearCacheAction
- mFlagManager.addListener(ReleasedFlag(1), listener)
+ mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
val observer = withArgCaptor<ContentObserver> {
verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
}
@@ -101,8 +101,8 @@
fun testObserverInvokesListeners() {
val listener1 = mock<FlagListenable.Listener>()
val listener10 = mock<FlagListenable.Listener>()
- mFlagManager.addListener(ReleasedFlag(1), listener1)
- mFlagManager.addListener(ReleasedFlag(10), listener10)
+ mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
+ mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
val observer = withArgCaptor<ContentObserver> {
verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
}
@@ -127,8 +127,8 @@
fun testOnlySpecificFlagListenerIsInvoked() {
val listener1 = mock<FlagListenable.Listener>()
val listener10 = mock<FlagListenable.Listener>()
- mFlagManager.addListener(ReleasedFlag(1), listener1)
- mFlagManager.addListener(ReleasedFlag(10), listener10)
+ mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
+ mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
mFlagManager.dispatchListenersAndMaybeRestart(1, null)
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -148,8 +148,8 @@
@Test
fun testSameListenerCanBeUsedForMultipleFlags() {
val listener = mock<FlagListenable.Listener>()
- mFlagManager.addListener(ReleasedFlag(1), listener)
- mFlagManager.addListener(ReleasedFlag(10), listener)
+ mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
+ mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener)
mFlagManager.dispatchListenersAndMaybeRestart(1, null)
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -177,7 +177,7 @@
@Test
fun testListenerCanSuppressRestart() {
val restartAction = mock<Consumer<Boolean>>()
- mFlagManager.addListener(ReleasedFlag(1)) { event ->
+ mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event ->
event.requestNoRestart()
}
mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
@@ -188,7 +188,7 @@
@Test
fun testListenerOnlySuppressesRestartForOwnFlag() {
val restartAction = mock<Consumer<Boolean>>()
- mFlagManager.addListener(ReleasedFlag(10)) { event ->
+ mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
event.requestNoRestart()
}
mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
@@ -199,10 +199,10 @@
@Test
fun testRestartWhenNotAllListenersRequestSuppress() {
val restartAction = mock<Consumer<Boolean>>()
- mFlagManager.addListener(ReleasedFlag(10)) { event ->
+ mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
event.requestNoRestart()
}
- mFlagManager.addListener(ReleasedFlag(10)) {
+ mFlagManager.addListener(ReleasedFlag(10, "10", "test")) {
// do not request
}
mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt
deleted file mode 100644
index 2b556f1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth
-import java.lang.StringBuilder
-import java.util.ArrayList
-import java.util.HashMap
-import org.junit.Test
-
-@SmallTest
-class FlagsTest : SysuiTestCase() {
- @Test
- fun testDuplicateFlagIdCheckWorks() {
- val flags = Flags.collectFlagsInClass(DuplicateFlagContainer)
- val duplicates = groupDuplicateFlags(flags)
- Truth.assertWithMessage(generateAssertionMessage(duplicates))
- .that(duplicates.size)
- .isEqualTo(2)
- }
-
- @Test
- fun testNoDuplicateFlagIds() {
- val flags = Flags.collectFlagsInClass(Flags)
- val duplicates = groupDuplicateFlags(flags)
- Truth.assertWithMessage(generateAssertionMessage(duplicates))
- .that(duplicates.size)
- .isEqualTo(0)
- }
-
- private fun generateAssertionMessage(duplicates: Map<Int, List<String>>): String {
- val stringBuilder = StringBuilder()
- stringBuilder.append("Duplicate flag keys found: {")
- for (id in duplicates.keys) {
- stringBuilder
- .append(" ")
- .append(id)
- .append(": [")
- .append(java.lang.String.join(", ", duplicates[id]))
- .append("]")
- }
- stringBuilder.append(" }")
- return stringBuilder.toString()
- }
-
- private fun groupDuplicateFlags(flags: Map<String, Flag<*>>): Map<Int, List<String>> {
- val grouping: MutableMap<Int, MutableList<String>> = HashMap()
- for (flag in flags) {
- grouping.putIfAbsent(flag.value.id, ArrayList())
- grouping[flag.value.id]!!.add(flag.key)
- }
- val result: MutableMap<Int, List<String>> = HashMap()
- for (id in grouping.keys) {
- if (grouping[id]!!.size > 1) {
- result[id] = grouping[id]!!
- }
- }
- return result
- }
-
- private object DuplicateFlagContainer {
- val A_FLAG: BooleanFlag = UnreleasedFlag(0)
- val B_FLAG: BooleanFlag = UnreleasedFlag(0)
- val C_FLAG = StringFlag(0)
- val D_FLAG: BooleanFlag = UnreleasedFlag(1)
- val E_FLAG = DoubleFlag(3)
- val F_FLAG = DoubleFlag(3)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 6f5f460..1633912 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -50,7 +50,7 @@
@Test
fun testChange_alertsListener() {
- val flag = ReleasedFlag(1)
+ val flag = ReleasedFlag(1, "1", "test")
serverFlagReader.listenForChanges(listOf(flag), changeListener)
deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 8b1554c..d52616b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -63,6 +63,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -103,6 +104,7 @@
@Mock private SecureSettings mSecureSettings;
@Mock private Resources mResources;
@Mock private ConfigurationController mConfigurationController;
+ @Mock private UserTracker mUserTracker;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private UserManager mUserManager;
@Mock private TrustManager mTrustManager;
@@ -152,6 +154,7 @@
mVibratorHelper,
mResources,
mConfigurationController,
+ mUserTracker,
mKeyguardStateController,
mUserManager,
mTrustManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
new file mode 100644
index 0000000..5e27a50
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -0,0 +1,445 @@
+/*
+ * 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.keyguard
+
+import android.content.ContentValues
+import android.content.pm.PackageManager
+import android.content.pm.ProviderInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SystemUIAppComponentFactoryBase
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: KeyguardQuickAffordanceProvider
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = KeyguardQuickAffordanceProvider()
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ configs =
+ setOf(
+ FakeKeyguardQuickAffordanceConfig(
+ key = AFFORDANCE_1,
+ pickerName = AFFORDANCE_1_NAME,
+ pickerIconResourceId = 1,
+ ),
+ FakeKeyguardQuickAffordanceConfig(
+ key = AFFORDANCE_2,
+ pickerName = AFFORDANCE_2_NAME,
+ pickerIconResourceId = 2,
+ ),
+ ),
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
+ )
+ underTest.interactor =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = FakeKeyguardRepository(),
+ ),
+ registry = mock(),
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ },
+ repository = { quickAffordanceRepository },
+ )
+
+ underTest.attachInfoForTesting(
+ context,
+ ProviderInfo().apply { authority = Contract.AUTHORITY },
+ )
+ context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
+ context.testablePermissions.setPermission(
+ Contract.PERMISSION,
+ PackageManager.PERMISSION_GRANTED,
+ )
+ }
+
+ @Test
+ fun `onAttachInfo - reportsContext`() {
+ val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
+ underTest.setContextAvailableCallback(callback)
+
+ underTest.attachInfo(context, null)
+
+ verify(callback).onContextAvailable(context)
+ }
+
+ @Test
+ fun getType() {
+ assertThat(underTest.getType(Contract.AffordanceTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd." +
+ "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
+ )
+ assertThat(underTest.getType(Contract.SlotTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
+ )
+ assertThat(underTest.getType(Contract.SelectionTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd." +
+ "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
+ )
+ }
+
+ @Test
+ fun `insert and query selection`() =
+ runBlocking(IMMEDIATE) {
+ val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ val affordanceId = AFFORDANCE_2
+ val affordanceName = AFFORDANCE_2_NAME
+
+ insertSelection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ affordanceName = affordanceName,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `query slots`() =
+ runBlocking(IMMEDIATE) {
+ assertThat(querySlots())
+ .isEqualTo(
+ listOf(
+ Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ capacity = 1,
+ ),
+ Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ capacity = 1,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `query affordances`() =
+ runBlocking(IMMEDIATE) {
+ assertThat(queryAffordances())
+ .isEqualTo(
+ listOf(
+ Affordance(
+ id = AFFORDANCE_1,
+ name = AFFORDANCE_1_NAME,
+ iconResourceId = 1,
+ ),
+ Affordance(
+ id = AFFORDANCE_2,
+ name = AFFORDANCE_2_NAME,
+ iconResourceId = 2,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `delete and query selection`() =
+ runBlocking(IMMEDIATE) {
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = AFFORDANCE_2,
+ )
+
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+ " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ AFFORDANCE_2,
+ ),
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ affordanceName = AFFORDANCE_1_NAME,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `delete all selections in a slot`() =
+ runBlocking(IMMEDIATE) {
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = AFFORDANCE_2,
+ )
+
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ Contract.SelectionTable.Columns.SLOT_ID,
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ ),
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ affordanceName = AFFORDANCE_1_NAME,
+ )
+ )
+ )
+ }
+
+ private fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ context.contentResolver.insert(
+ Contract.SelectionTable.URI,
+ ContentValues().apply {
+ put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+ put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+ }
+ )
+ }
+
+ private fun querySelections(): List<Selection> {
+ return context.contentResolver
+ .query(
+ Contract.SelectionTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val slotIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+ val affordanceNameColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+ if (
+ slotIdColumnIndex == -1 ||
+ affordanceIdColumnIndex == -1 ||
+ affordanceNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Selection(
+ slotId = cursor.getString(slotIdColumnIndex),
+ affordanceId = cursor.getString(affordanceIdColumnIndex),
+ affordanceName = cursor.getString(affordanceNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ private fun querySlots(): List<Slot> {
+ return context.contentResolver
+ .query(
+ Contract.SlotTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+ val capacityColumnIndex =
+ cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+ if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Slot(
+ id = cursor.getString(idColumnIndex),
+ capacity = cursor.getInt(capacityColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ private fun queryAffordances(): List<Affordance> {
+ return context.contentResolver
+ .query(
+ Contract.AffordanceTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+ val iconColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+ if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Affordance(
+ id = cursor.getString(idColumnIndex),
+ name = cursor.getString(nameColumnIndex),
+ iconResourceId = cursor.getInt(iconColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ data class Slot(
+ val id: String,
+ val capacity: Int,
+ )
+
+ data class Affordance(
+ val id: String,
+ val name: String,
+ val iconResourceId: Int,
+ )
+
+ data class Selection(
+ val slotId: String,
+ val affordanceId: String,
+ val affordanceName: String,
+ )
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val AFFORDANCE_1 = "affordance_1"
+ private const val AFFORDANCE_2 = "affordance_2"
+ private const val AFFORDANCE_1_NAME = "affordance_1_name"
+ private const val AFFORDANCE_2_NAME = "affordance_2_name"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 23516c9..729a1cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.SystemUIInitializerImpl;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -93,6 +94,8 @@
private NextAlarmController mNextAlarmController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private UserTracker mUserTracker;
private TestableKeyguardSliceProvider mProvider;
private boolean mIsZenMode;
@@ -105,6 +108,7 @@
mProvider.attachInfo(getContext(), null);
reset(mContentResolver);
SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
+ when(mUserTracker.getUserId()).thenReturn(100);
}
@After
@@ -267,6 +271,7 @@
mKeyguardBypassController = KeyguardSliceProviderTest.this.mKeyguardBypassController;
mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
+ mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b6780a1..d17e374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -64,6 +65,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -86,6 +88,7 @@
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
+ private @Mock UserTracker mUserTracker;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock KeyguardUpdateMonitor mUpdateMonitor;
@@ -110,6 +113,7 @@
private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ private @Mock ScrimController mScrimController;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -286,6 +290,7 @@
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
+ mUserTracker,
mFalsingCollector,
mLockPatternUtils,
mBroadcastDispatcher,
@@ -311,7 +316,8 @@
mDreamOverlayStateController,
() -> mShadeController,
mNotificationShadeWindowControllerLazy,
- () -> mActivityLaunchAnimator);
+ () -> mActivityLaunchAnimator,
+ () -> mScrimController);
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..623becf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraGestureHelper
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
+ @Mock private lateinit var context: Context
+ private lateinit var underTest: CameraQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = CameraQuickAffordanceConfig(
+ context,
+ cameraGestureHelper,
+ )
+ }
+
+ @Test
+ fun `affordance triggered -- camera launch called`() {
+ //when
+ val result = underTest.onTriggered(null)
+
+ //then
+ verify(cameraGestureHelper)
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
new file mode 100644
index 0000000..8ef921e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.res.Resources
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.FakeSharedPreferences
+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.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
+
+ @Mock private lateinit var sharedPrefs: FakeSharedPreferences
+
+ private lateinit var underTest: KeyguardQuickAffordanceLegacySettingSyncer
+
+ private lateinit var testScope: TestScope
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var selectionManager: KeyguardQuickAffordanceSelectionManager
+ private lateinit var settings: FakeSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ val context: Context = mock()
+ sharedPrefs = FakeSharedPreferences()
+ whenever(context.getSharedPreferences(anyString(), any())).thenReturn(sharedPrefs)
+ val resources: Resources = mock()
+ whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
+ .thenReturn(emptyArray())
+ whenever(context.resources).thenReturn(resources)
+
+ testDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = FakeUserTracker(),
+ )
+ settings = FakeSettings()
+ settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
+ settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET, 0)
+ settings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0)
+
+ underTest =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = testScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettings = settings,
+ selectionsManager = selectionManager,
+ )
+ }
+
+ @Test
+ fun `Setting a setting selects the affordance`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 1,
+ )
+
+ assertThat(
+ selectionManager
+ .getSelections()
+ .getOrDefault(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ emptyList()
+ )
+ )
+ .contains(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Clearing a setting selects the affordance`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 1,
+ )
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 0,
+ )
+
+ assertThat(
+ selectionManager
+ .getSelections()
+ .getOrDefault(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ emptyList()
+ )
+ )
+ .doesNotContain(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Selecting an affordance sets its setting`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+ )
+
+ advanceUntilIdle()
+ assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Unselecting an affordance clears its setting`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+ )
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ emptyList()
+ )
+
+ assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(0)
+
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
index d2422ad..d8ee9f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -17,111 +17,312 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+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.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+ @Mock private lateinit var userFileManager: UserFileManager
+
private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
+
@Before
fun setUp() {
- underTest = KeyguardQuickAffordanceSelectionManager()
+ MockitoAnnotations.initMocks(this)
+ sharedPrefs = mutableMapOf()
+ whenever(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())).thenAnswer {
+ val userId = it.arguments[2] as Int
+ sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
+ }
+ userTracker = FakeUserTracker()
+
+ underTest =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager = userFileManager,
+ userTracker = userTracker,
+ )
}
@Test
- fun setSelections() =
- runBlocking(IMMEDIATE) {
- var affordanceIdsBySlotId: Map<String, List<String>>? = null
- val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
- val slotId1 = "slot1"
- val slotId2 = "slot2"
- val affordanceId1 = "affordance1"
- val affordanceId2 = "affordance2"
- val affordanceId3 = "affordance3"
+ fun setSelections() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId1),
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ ),
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(affordanceId2),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ slotId2 to listOf(affordanceId2),
)
- assertSelections(
- affordanceIdsBySlotId,
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1, affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `remembers selections by user`() = runTest {
+ val slot1 = "slot_1"
+ val slot2 = "slot_2"
+ val affordance1 = "affordance_1"
+ val affordance2 = "affordance_2"
+ val affordance3 = "affordance_3"
+
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ val userInfos =
+ listOf(
+ UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+ UserInfo(/* id= */ 1, "one", /* flags= */ 0),
+ )
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ underTest.setSelections(
+ slotId = slot1,
+ affordanceIds = listOf(affordance1),
+ )
+ underTest.setSelections(
+ slotId = slot2,
+ affordanceIds = listOf(affordance2),
+ )
+
+ // Switch to user 1
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 1,
+ )
+ // We never set selections on user 1, so it should be empty.
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected = emptyMap(),
+ )
+ // Now, let's set selections on user 1.
+ underTest.setSelections(
+ slotId = slot1,
+ affordanceIds = listOf(affordance2),
+ )
+ underTest.setSelections(
+ slotId = slot2,
+ affordanceIds = listOf(affordance3),
+ )
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected =
mapOf(
- slotId1 to listOf(affordanceId1),
+ slot1 to listOf(affordance2),
+ slot2 to listOf(affordance3),
),
- )
+ )
- underTest.setSelections(
- slotId = slotId2,
- affordanceIds = listOf(affordanceId2),
- )
- assertSelections(
- affordanceIdsBySlotId,
+ // Switch back to user 0.
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ // Assert that we still remember the old selections for user 0.
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected =
mapOf(
- slotId1 to listOf(affordanceId1),
- slotId2 to listOf(affordanceId2),
- )
- )
+ slot1 to listOf(affordance1),
+ slot2 to listOf(affordance2),
+ ),
+ )
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId1, affordanceId3),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId1, affordanceId3),
- slotId2 to listOf(affordanceId2),
- )
- )
+ job.cancel()
+ }
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId3),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId3),
- slotId2 to listOf(affordanceId2),
- )
- )
+ @Test
+ fun `selections respects defaults`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
- underTest.setSelections(
- slotId = slotId2,
- affordanceIds = listOf(),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId3),
- slotId2 to listOf(),
- )
- )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
- job.cancel()
- }
+ job.cancel()
+ }
- private suspend fun assertSelections(
+ @Test
+ fun `selections ignores defaults after selecting an affordance`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ underTest.setSelections(slotId1, listOf(affordanceId2))
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId2),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections ignores defaults after clearing a slot`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ underTest.setSelections(slotId1, listOf())
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ private fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
) {
assertThat(underTest.getSelections()).isEqualTo(expected)
assertThat(observed).isEqualTo(expected)
}
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5a7f2bb..d8a3605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -18,13 +18,20 @@
package com.android.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -36,6 +43,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -51,11 +60,36 @@
fun setUp() {
config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = FakeUserTracker(),
+ )
+
underTest =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs = setOf(config1, config2),
)
}
@@ -119,16 +153,32 @@
@Test
fun getSlotPickerRepresentations() {
+ val slot1 = "slot1"
+ val slot2 = "slot2"
+ val slot3 = "slot3"
+ context.orCreateTestableResources.addOverride(
+ R.array.config_keyguardQuickAffordanceSlots,
+ arrayOf(
+ "$slot1:2",
+ "$slot2:4",
+ "$slot3:5",
+ ),
+ )
+
assertThat(underTest.getSlotPickerRepresentations())
.isEqualTo(
listOf(
KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- maxSelectedAffordances = 1,
+ id = slot1,
+ maxSelectedAffordances = 2,
),
KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- maxSelectedAffordances = 1,
+ id = slot2,
+ maxSelectedAffordances = 4,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = slot3,
+ maxSelectedAffordances = 5,
),
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 53d9b87..13fc9fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -17,21 +17,30 @@
package com.android.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,7 +57,9 @@
@Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
private lateinit var underTest: KeyguardRepositoryImpl
@@ -59,225 +70,354 @@
underTest =
KeyguardRepositoryImpl(
statusBarStateController,
- keyguardStateController,
dozeHost,
wakefulnessLifecycle,
biometricUnlockController,
+ keyguardStateController,
+ keyguardUpdateMonitor,
+ dozeTransitionListener,
)
}
@Test
- fun animateBottomAreaDozingTransitions() = runBlockingTest {
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+ fun animateBottomAreaDozingTransitions() =
+ runTest(UnconfinedTestDispatcher()) {
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
- underTest.setAnimateDozingTransitions(true)
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
- underTest.setAnimateDozingTransitions(false)
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+ underTest.setAnimateDozingTransitions(false)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
- underTest.setAnimateDozingTransitions(true)
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
- }
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+ }
@Test
- fun bottomAreaAlpha() = runBlockingTest {
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+ fun bottomAreaAlpha() =
+ runTest(UnconfinedTestDispatcher()) {
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
- underTest.setBottomAreaAlpha(0.1f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+ underTest.setBottomAreaAlpha(0.1f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
- underTest.setBottomAreaAlpha(0.2f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+ underTest.setBottomAreaAlpha(0.2f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
- underTest.setBottomAreaAlpha(0.3f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+ underTest.setBottomAreaAlpha(0.3f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
- underTest.setBottomAreaAlpha(0.5f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+ underTest.setBottomAreaAlpha(0.5f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
- underTest.setBottomAreaAlpha(1.0f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
- }
+ underTest.setBottomAreaAlpha(1.0f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+ }
@Test
- fun clockPosition() = runBlockingTest {
- assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+ fun clockPosition() =
+ runTest(UnconfinedTestDispatcher()) {
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
- underTest.setClockPosition(0, 1)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+ underTest.setClockPosition(0, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
- underTest.setClockPosition(1, 9)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+ underTest.setClockPosition(1, 9)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
- underTest.setClockPosition(1, 0)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+ underTest.setClockPosition(1, 0)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
- underTest.setClockPosition(3, 1)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
- }
+ underTest.setClockPosition(3, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+ }
@Test
- fun isKeyguardShowing() = runBlockingTest {
- whenever(keyguardStateController.isShowing).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+ fun isKeyguardShowing() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
- assertThat(underTest.isKeyguardShowing()).isFalse()
+ assertThat(latest).isFalse()
+ assertThat(underTest.isKeyguardShowing()).isFalse()
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
- whenever(keyguardStateController.isShowing).thenReturn(true)
- captor.value.onKeyguardShowingChanged()
- assertThat(latest).isTrue()
- assertThat(underTest.isKeyguardShowing()).isTrue()
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isTrue()
+ assertThat(underTest.isKeyguardShowing()).isTrue()
- whenever(keyguardStateController.isShowing).thenReturn(false)
- captor.value.onKeyguardShowingChanged()
- assertThat(latest).isFalse()
- assertThat(underTest.isKeyguardShowing()).isFalse()
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isFalse()
+ assertThat(underTest.isKeyguardShowing()).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isDozing() = runBlockingTest {
- var latest: Boolean? = null
- val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ fun isDozing() =
+ runTest(UnconfinedTestDispatcher()) {
+ var latest: Boolean? = null
+ val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
- val captor = argumentCaptor<DozeHost.Callback>()
- verify(dozeHost).addCallback(captor.capture())
+ val captor = argumentCaptor<DozeHost.Callback>()
+ verify(dozeHost).addCallback(captor.capture())
- captor.value.onDozingChanged(true)
- assertThat(latest).isTrue()
+ captor.value.onDozingChanged(true)
+ assertThat(latest).isTrue()
- captor.value.onDozingChanged(false)
- assertThat(latest).isFalse()
+ captor.value.onDozingChanged(false)
+ assertThat(latest).isFalse()
- job.cancel()
- verify(dozeHost).removeCallback(captor.value)
- }
+ job.cancel()
+ verify(dozeHost).removeCallback(captor.value)
+ }
@Test
- fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
- var latest: Boolean? = null
+ fun `isDozing - starts with correct initial value for isDozing`() =
+ runTest(UnconfinedTestDispatcher()) {
+ var latest: Boolean? = null
- whenever(statusBarStateController.isDozing).thenReturn(true)
- var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
- assertThat(latest).isTrue()
- job.cancel()
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isTrue()
+ job.cancel()
- whenever(statusBarStateController.isDozing).thenReturn(false)
- job = underTest.isDozing.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
- job.cancel()
- }
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isFalse()
+ job.cancel()
+ }
@Test
- fun dozeAmount() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+ fun dozeAmount() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+ val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
- val captor = argumentCaptor<StatusBarStateController.StateListener>()
- verify(statusBarStateController).addCallback(captor.capture())
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
- captor.value.onDozeAmountChanged(0.433f, 0.4f)
- captor.value.onDozeAmountChanged(0.498f, 0.5f)
- captor.value.onDozeAmountChanged(0.661f, 0.65f)
+ captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ captor.value.onDozeAmountChanged(0.661f, 0.65f)
- assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+ assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
- job.cancel()
- verify(statusBarStateController).removeCallback(captor.value)
- }
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
@Test
- fun wakefulness() = runBlockingTest {
- val values = mutableListOf<WakefulnessModel>()
- val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
+ fun wakefulness() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<WakefulnessModel>()
+ val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
- val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
- verify(wakefulnessLifecycle).addObserver(captor.capture())
+ val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
- captor.value.onStartedWakingUp()
- captor.value.onFinishedWakingUp()
- captor.value.onStartedGoingToSleep()
- captor.value.onFinishedGoingToSleep()
+ captor.value.onStartedWakingUp()
+ captor.value.onFinishedWakingUp()
+ captor.value.onStartedGoingToSleep()
+ captor.value.onFinishedGoingToSleep()
- assertThat(values)
- .isEqualTo(
- listOf(
- // Initial value will be ASLEEP
- WakefulnessModel.ASLEEP,
- WakefulnessModel.STARTING_TO_WAKE,
- WakefulnessModel.AWAKE,
- WakefulnessModel.STARTING_TO_SLEEP,
- WakefulnessModel.ASLEEP,
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be ASLEEP
+ WakefulnessModel.ASLEEP,
+ WakefulnessModel.STARTING_TO_WAKE,
+ WakefulnessModel.AWAKE,
+ WakefulnessModel.STARTING_TO_SLEEP,
+ WakefulnessModel.ASLEEP,
+ )
)
- )
- job.cancel()
- verify(wakefulnessLifecycle).removeObserver(captor.value)
- }
+ job.cancel()
+ verify(wakefulnessLifecycle).removeObserver(captor.value)
+ }
@Test
- fun isBouncerShowing() = runBlockingTest {
- whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+ fun isUdfpsSupported() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
+ assertThat(underTest.isUdfpsSupported()).isTrue()
- assertThat(latest).isFalse()
-
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
-
- whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
- captor.value.onBouncerShowingChanged()
- assertThat(latest).isTrue()
-
- whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
- captor.value.onBouncerShowingChanged()
- assertThat(latest).isFalse()
-
- job.cancel()
- }
+ whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
+ assertThat(underTest.isUdfpsSupported()).isFalse()
+ }
@Test
- fun biometricUnlockState() = runBlockingTest {
- val values = mutableListOf<BiometricUnlockModel>()
- val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+ fun isBouncerShowing() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
- val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
- verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+ assertThat(latest).isFalse()
- captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
- assertThat(values)
- .isEqualTo(
- listOf(
- // Initial value will be NONE, followed by onModeChanged() call
- BiometricUnlockModel.NONE,
- BiometricUnlockModel.NONE,
- BiometricUnlockModel.WAKE_AND_UNLOCK,
- BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
- BiometricUnlockModel.SHOW_BOUNCER,
- BiometricUnlockModel.ONLY_WAKE,
- BiometricUnlockModel.UNLOCK_COLLAPSING,
- BiometricUnlockModel.DISMISS_BOUNCER,
- BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isKeyguardGoingAway() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
+ captor.value.onKeyguardGoingAwayChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+ captor.value.onKeyguardGoingAwayChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDreaming() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+ captor.value.onDreamingStateChanged(true)
+ assertThat(latest).isTrue()
+
+ captor.value.onDreamingStateChanged(false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun biometricUnlockState() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<BiometricUnlockModel>()
+ val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+ verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+ captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be NONE, followed by onModeChanged() call
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.SHOW_BOUNCER,
+ BiometricUnlockModel.ONLY_WAKE,
+ BiometricUnlockModel.UNLOCK_COLLAPSING,
+ BiometricUnlockModel.DISMISS_BOUNCER,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ )
)
+
+ job.cancel()
+ verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+ }
+
+ @Test
+ fun dozeTransitionModel() =
+ runTest(UnconfinedTestDispatcher()) {
+ // For the initial state
+ whenever(dozeTransitionListener.oldState).thenReturn(DozeMachine.State.UNINITIALIZED)
+ whenever(dozeTransitionListener.newState).thenReturn(DozeMachine.State.UNINITIALIZED)
+
+ val values = mutableListOf<DozeTransitionModel>()
+ val job = underTest.dozeTransitionModel.onEach(values::add).launchIn(this)
+
+ val listener =
+ withArgCaptor<DozeTransitionCallback> {
+ verify(dozeTransitionListener).addCallback(capture())
+ }
+
+ // These don't have to reflect real transitions from the DozeMachine. Only that the
+ // transitions are properly emitted
+ listener.onDozeTransition(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE)
+ listener.onDozeTransition(DozeMachine.State.DOZE, DozeMachine.State.DOZE_AOD)
+ listener.onDozeTransition(DozeMachine.State.DOZE_AOD_DOCKED, DozeMachine.State.FINISH)
+ listener.onDozeTransition(
+ DozeMachine.State.DOZE_REQUEST_PULSE,
+ DozeMachine.State.DOZE_PULSING
+ )
+ listener.onDozeTransition(
+ DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
+ DozeMachine.State.DOZE_PULSE_DONE
+ )
+ listener.onDozeTransition(
+ DozeMachine.State.DOZE_AOD_PAUSING,
+ DozeMachine.State.DOZE_AOD_PAUSED
)
- job.cancel()
- verify(biometricUnlockController).removeBiometricModeListener(captor.value)
- }
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be UNINITIALIZED
+ DozeTransitionModel(
+ DozeStateModel.UNINITIALIZED,
+ DozeStateModel.UNINITIALIZED
+ ),
+ DozeTransitionModel(DozeStateModel.INITIALIZED, DozeStateModel.DOZE),
+ DozeTransitionModel(DozeStateModel.DOZE, DozeStateModel.DOZE_AOD),
+ DozeTransitionModel(DozeStateModel.DOZE_AOD_DOCKED, DozeStateModel.FINISH),
+ DozeTransitionModel(
+ DozeStateModel.DOZE_REQUEST_PULSE,
+ DozeStateModel.DOZE_PULSING
+ ),
+ DozeTransitionModel(
+ DozeStateModel.DOZE_SUSPEND_TRIGGERS,
+ DozeStateModel.DOZE_PULSE_DONE
+ ),
+ DozeTransitionModel(
+ DozeStateModel.DOZE_AOD_PAUSING,
+ DozeStateModel.DOZE_AOD_PAUSED
+ ),
+ )
+ )
+
+ job.cancel()
+ verify(dozeTransitionListener).removeCallback(listener)
+ }
}
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 27d5d0a..2b03722 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
@@ -25,8 +25,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -38,7 +38,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
@@ -91,18 +90,51 @@
}
}
- assertSteps(steps, listWithStep(BigDecimal(.1)))
+ assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
job.cancel()
provider.stop()
}
@Test
- fun `startTransition called during another transition fails`() {
- underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
- underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+ fun `starting second transition will cancel the first transition`() {
+ runBlocking(IMMEDIATE) {
+ val (animator, provider) = setupAnimator(this)
- assertThat(wtfHandler.failed).isTrue()
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+ // 3 yields(), alternating with the animator, results in a value 0.1, which can be
+ // canceled and tested against
+ yield()
+ yield()
+ yield()
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ val (animator2, provider2) = setupAnimator(this)
+ underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2))
+
+ val startTime = System.currentTimeMillis()
+ while (animator2.isRunning()) {
+ yield()
+ if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+ fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+ }
+ }
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ provider.stop()
+ provider2.stop()
+ }
}
@Test
@@ -165,11 +197,15 @@
assertThat(wtfHandler.failed).isTrue()
}
- private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+ private fun listWithStep(
+ step: BigDecimal,
+ start: BigDecimal = BigDecimal.ZERO,
+ stop: BigDecimal = BigDecimal.ONE,
+ ): List<BigDecimal> {
val steps = mutableListOf<BigDecimal>()
- var i = BigDecimal.ZERO
- while (i.compareTo(BigDecimal.ONE) <= 0) {
+ var i = start
+ while (i.compareTo(stop) <= 0) {
steps.add(i)
i = (i + step).setScale(2, RoundingMode.HALF_UP)
}
@@ -177,23 +213,43 @@
return steps
}
- private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
+ private fun assertSteps(
+ steps: List<TransitionStep>,
+ fractions: List<BigDecimal>,
+ from: KeyguardState,
+ to: KeyguardState,
+ ) {
assertThat(steps[0])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
+ .isEqualTo(
+ TransitionStep(
+ from,
+ to,
+ fractions[0].toFloat(),
+ TransitionState.STARTED,
+ OWNER_NAME
+ )
+ )
fractions.forEachIndexed { index, fraction ->
assertThat(steps[index + 1])
.isEqualTo(
TransitionStep(
- AOD,
- LOCKSCREEN,
+ from,
+ to,
fraction.toFloat(),
TransitionState.RUNNING,
OWNER_NAME
)
)
}
+ val lastValue = fractions[fractions.size - 1].toFloat()
+ val status =
+ if (lastValue < 1f) {
+ TransitionState.CANCELED
+ } else {
+ TransitionState.FINISHED
+ }
assertThat(steps[steps.size - 1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
+ .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME))
assertThat(wtfHandler.failed).isFalse()
}
@@ -230,7 +286,7 @@
scope.launch {
frames.collect {
// Delay is required for AnimationHandler to properly register a callback
- delay(1)
+ yield()
val (frameNumber, callback) = it
callback?.doFrame(frameNumber)
}
@@ -243,7 +299,7 @@
}
override fun postFrameCallback(cb: FrameCallback) {
- frames.value = Pair(++frameCount, cb)
+ frames.value = Pair(frameCount++, cb)
}
override fun postCommitCallback(runnable: Runnable) {}
override fun getFrameTime() = frameCount
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
deleted file mode 100644
index 3a61c57..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
+++ /dev/null
@@ -1,86 +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.keyguard.domain.interactor
-
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.KeyguardBouncer
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class BouncerCallbackInteractorTest : SysuiTestCase() {
- private val bouncerCallbackInteractor = BouncerCallbackInteractor()
- @Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback
- @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback)
- bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
- }
-
- @Test
- fun testOnFullyShown() {
- bouncerCallbackInteractor.dispatchFullyShown()
- verify(bouncerExpansionCallback).onFullyShown()
- }
-
- @Test
- fun testOnFullyHidden() {
- bouncerCallbackInteractor.dispatchFullyHidden()
- verify(bouncerExpansionCallback).onFullyHidden()
- }
-
- @Test
- fun testOnExpansionChanged() {
- bouncerCallbackInteractor.dispatchExpansionChanged(5f)
- verify(bouncerExpansionCallback).onExpansionChanged(5f)
- }
-
- @Test
- fun testOnVisibilityChanged() {
- bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
- verify(bouncerExpansionCallback).onVisibilityChanged(false)
- }
-
- @Test
- fun testOnStartingToHide() {
- bouncerCallbackInteractor.dispatchStartingToHide()
- verify(bouncerExpansionCallback).onStartingToHide()
- }
-
- @Test
- fun testOnStartingToShow() {
- bouncerCallbackInteractor.dispatchStartingToShow()
- verify(bouncerExpansionCallback).onStartingToShow()
- }
-
- @Test
- fun testOnKeyguardReset() {
- bouncerCallbackInteractor.dispatchReset()
- verify(keyguardResetCallback).onKeyguardReset()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
deleted file mode 100644
index 5743b2f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ /dev/null
@@ -1,277 +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.keyguard.domain.interactor
-
-import android.os.Looper
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.DejankUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Answers
-import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class BouncerInteractorTest : SysuiTestCase() {
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private lateinit var repository: KeyguardBouncerRepository
- @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
- @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
- @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
- @Mock private lateinit var falsingCollector: FalsingCollector
- @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
- @Mock private lateinit var keyguardBypassController: KeyguardBypassController
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- private val mainHandler = FakeHandler(Looper.getMainLooper())
- private lateinit var bouncerInteractor: BouncerInteractor
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- DejankUtils.setImmediate(true)
- bouncerInteractor =
- BouncerInteractor(
- repository,
- bouncerView,
- mainHandler,
- keyguardStateController,
- keyguardSecurityModel,
- bouncerCallbackInteractor,
- falsingCollector,
- dismissCallbackRegistry,
- keyguardBypassController,
- keyguardUpdateMonitor,
- )
- `when`(repository.startingDisappearAnimation.value).thenReturn(null)
- `when`(repository.show.value).thenReturn(null)
- `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
- }
-
- @Test
- fun testShow_isScrimmed() {
- bouncerInteractor.show(true)
- verify(repository).setShowMessage(null)
- verify(repository).setOnScreenTurnedOff(false)
- verify(repository).setKeyguardAuthenticated(null)
- verify(repository).setHide(false)
- verify(repository).setStartingToHide(false)
- verify(repository).setScrimmed(true)
- verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
- verify(repository).setShowingSoon(true)
- verify(keyguardStateController).notifyBouncerShowing(true)
- verify(bouncerCallbackInteractor).dispatchStartingToShow()
- verify(repository).setVisible(true)
- verify(repository).setShow(any(KeyguardBouncerModel::class.java))
- verify(repository).setShowingSoon(false)
- }
-
- @Test
- fun testShow_isNotScrimmed() {
- verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
- }
-
- @Test
- fun testShow_keyguardIsDone() {
- `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
- verify(keyguardStateController, never()).notifyBouncerShowing(true)
- verify(bouncerCallbackInteractor, never()).dispatchStartingToShow()
- }
-
- @Test
- fun testHide() {
- bouncerInteractor.hide()
- verify(falsingCollector).onBouncerHidden()
- verify(keyguardStateController).notifyBouncerShowing(false)
- verify(repository).setShowingSoon(false)
- verify(repository).setVisible(false)
- verify(repository).setHide(true)
- verify(repository).setShow(null)
- }
-
- @Test
- fun testExpansion() {
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- bouncerInteractor.setPanelExpansion(0.6f)
- verify(repository).setPanelExpansion(0.6f)
- verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
- }
-
- @Test
- fun testExpansion_fullyShown() {
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- `when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
- verify(falsingCollector).onBouncerShown()
- verify(bouncerCallbackInteractor).dispatchFullyShown()
- }
-
- @Test
- fun testExpansion_fullyHidden() {
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- `when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
- verify(repository).setVisible(false)
- verify(repository).setShow(null)
- verify(falsingCollector).onBouncerHidden()
- verify(bouncerCallbackInteractor).dispatchReset()
- verify(bouncerCallbackInteractor).dispatchFullyHidden()
- }
-
- @Test
- fun testExpansion_startingToHide() {
- `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- bouncerInteractor.setPanelExpansion(0.1f)
- verify(repository).setStartingToHide(true)
- verify(bouncerCallbackInteractor).dispatchStartingToHide()
- }
-
- @Test
- fun testShowMessage() {
- bouncerInteractor.showMessage("abc", null)
- verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
- }
-
- @Test
- fun testDismissAction() {
- val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
- val cancelAction = mock(Runnable::class.java)
- bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
- verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
- }
-
- @Test
- fun testUpdateResources() {
- bouncerInteractor.updateResources()
- verify(repository).setResourceUpdateRequests(true)
- }
-
- @Test
- fun testNotifyKeyguardAuthenticated() {
- bouncerInteractor.notifyKeyguardAuthenticated(true)
- verify(repository).setKeyguardAuthenticated(true)
- }
-
- @Test
- fun testOnScreenTurnedOff() {
- bouncerInteractor.onScreenTurnedOff()
- verify(repository).setOnScreenTurnedOff(true)
- }
-
- @Test
- fun testSetKeyguardPosition() {
- bouncerInteractor.setKeyguardPosition(0f)
- verify(repository).setKeyguardPosition(0f)
- }
-
- @Test
- fun testNotifyKeyguardAuthenticatedHandled() {
- bouncerInteractor.notifyKeyguardAuthenticatedHandled()
- verify(repository).setKeyguardAuthenticated(null)
- }
-
- @Test
- fun testNotifyUpdatedResources() {
- bouncerInteractor.notifyUpdatedResources()
- verify(repository).setResourceUpdateRequests(false)
- }
-
- @Test
- fun testSetBackButtonEnabled() {
- bouncerInteractor.setBackButtonEnabled(true)
- verify(repository).setIsBackButtonEnabled(true)
- }
-
- @Test
- fun testStartDisappearAnimation() {
- val runnable = mock(Runnable::class.java)
- bouncerInteractor.startDisappearAnimation(runnable)
- verify(repository).setStartDisappearAnimation(any(Runnable::class.java))
- }
-
- @Test
- fun testIsFullShowing() {
- `when`(repository.isVisible.value).thenReturn(true)
- `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- `when`(repository.startingDisappearAnimation.value).thenReturn(null)
- assertThat(bouncerInteractor.isFullyShowing()).isTrue()
- `when`(repository.isVisible.value).thenReturn(false)
- assertThat(bouncerInteractor.isFullyShowing()).isFalse()
- }
-
- @Test
- fun testIsScrimmed() {
- `when`(repository.isScrimmed.value).thenReturn(true)
- assertThat(bouncerInteractor.isScrimmed()).isTrue()
- `when`(repository.isScrimmed.value).thenReturn(false)
- assertThat(bouncerInteractor.isScrimmed()).isFalse()
- }
-
- @Test
- fun testIsInTransit() {
- `when`(repository.showingSoon.value).thenReturn(true)
- assertThat(bouncerInteractor.isInTransit()).isTrue()
- `when`(repository.showingSoon.value).thenReturn(false)
- assertThat(bouncerInteractor.isInTransit()).isFalse()
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- assertThat(bouncerInteractor.isInTransit()).isTrue()
- }
-
- @Test
- fun testIsAnimatingAway() {
- `when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {})
- assertThat(bouncerInteractor.isAnimatingAway()).isTrue()
- `when`(repository.startingDisappearAnimation.value).thenReturn(null)
- assertThat(bouncerInteractor.isAnimatingAway()).isFalse()
- }
-
- @Test
- fun testWillDismissWithAction() {
- `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
- assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
- `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
- assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 8b6603d..1e1d3f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -30,17 +30,22 @@
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
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.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runBlockingTest
@@ -50,6 +55,8 @@
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.same
import org.mockito.Mock
@@ -201,7 +208,6 @@
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
@Mock private lateinit var expandable: Expandable
@@ -214,12 +220,14 @@
@JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
@JvmField @Parameter(4) var startActivity: Boolean = false
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var userTracker: UserTracker
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
+ userTracker = FakeUserTracker()
homeControls =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
val quickAccessWallet =
@@ -228,11 +236,35 @@
)
val qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
)
underTest =
@@ -318,7 +350,6 @@
needStrongAuthAfterBoot: Boolean = true,
keyguardIsUnlocked: Boolean = false,
) {
- whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(any()))
.thenReturn(
if (needStrongAuthAfterBoot) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 3364535..4850ea5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,19 +27,24 @@
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -53,6 +58,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@@ -89,12 +96,36 @@
)
qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
)
featureFlags =
@@ -284,7 +315,13 @@
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
- listOf(homeControls.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = homeControls.key,
+ name = homeControls.pickerName,
+ iconResourceId = homeControls.pickerIconResourceId,
+ ),
+ ),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
@@ -313,7 +350,13 @@
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
@@ -345,9 +388,21 @@
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
- listOf(qrCodeScanner.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = qrCodeScanner.key,
+ name = qrCodeScanner.pickerName,
+ iconResourceId = qrCodeScanner.pickerIconResourceId,
+ ),
+ ),
)
)
@@ -411,7 +466,13 @@
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
)
)
@@ -472,7 +533,13 @@
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
new file mode 100644
index 0000000..db9c4e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
+ private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
+ @Mock
+ private lateinit var mPrimaryBouncerExpansionCallback:
+ KeyguardBouncer.PrimaryBouncerExpansionCallback
+ @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(
+ mPrimaryBouncerExpansionCallback
+ )
+ mPrimaryBouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
+ }
+
+ @Test
+ fun testOnFullyShown() {
+ mPrimaryBouncerCallbackInteractor.dispatchFullyShown()
+ verify(mPrimaryBouncerExpansionCallback).onFullyShown()
+ }
+
+ @Test
+ fun testOnFullyHidden() {
+ mPrimaryBouncerCallbackInteractor.dispatchFullyHidden()
+ verify(mPrimaryBouncerExpansionCallback).onFullyHidden()
+ }
+
+ @Test
+ fun testOnExpansionChanged() {
+ mPrimaryBouncerCallbackInteractor.dispatchExpansionChanged(5f)
+ verify(mPrimaryBouncerExpansionCallback).onExpansionChanged(5f)
+ }
+
+ @Test
+ fun testOnVisibilityChanged() {
+ mPrimaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+ verify(mPrimaryBouncerExpansionCallback).onVisibilityChanged(false)
+ }
+
+ @Test
+ fun testOnStartingToHide() {
+ mPrimaryBouncerCallbackInteractor.dispatchStartingToHide()
+ verify(mPrimaryBouncerExpansionCallback).onStartingToHide()
+ }
+
+ @Test
+ fun testOnStartingToShow() {
+ mPrimaryBouncerCallbackInteractor.dispatchStartingToShow()
+ verify(mPrimaryBouncerExpansionCallback).onStartingToShow()
+ }
+
+ @Test
+ fun testOnKeyguardReset() {
+ mPrimaryBouncerCallbackInteractor.dispatchReset()
+ verify(keyguardResetCallback).onKeyguardReset()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
new file mode 100644
index 0000000..559f183
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -0,0 +1,286 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class PrimaryBouncerInteractorTest : SysuiTestCase() {
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private lateinit var repository: KeyguardBouncerRepository
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+ @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ private val mainHandler = FakeHandler(Looper.getMainLooper())
+ private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ DejankUtils.setImmediate(true)
+ mPrimaryBouncerInteractor =
+ PrimaryBouncerInteractor(
+ repository,
+ bouncerView,
+ mainHandler,
+ keyguardStateController,
+ keyguardSecurityModel,
+ mPrimaryBouncerCallbackInteractor,
+ falsingCollector,
+ dismissCallbackRegistry,
+ keyguardBypassController,
+ keyguardUpdateMonitor,
+ )
+ `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ `when`(repository.primaryBouncerShow.value).thenReturn(null)
+ `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+ }
+
+ @Test
+ fun testShow_isScrimmed() {
+ mPrimaryBouncerInteractor.show(true)
+ verify(repository).setOnScreenTurnedOff(false)
+ verify(repository).setKeyguardAuthenticated(null)
+ verify(repository).setPrimaryHide(false)
+ verify(repository).setPrimaryStartingToHide(false)
+ verify(repository).setPrimaryScrimmed(true)
+ verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
+ verify(repository).setPrimaryShowingSoon(true)
+ verify(keyguardStateController).notifyBouncerShowing(true)
+ verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
+ verify(repository).setPrimaryVisible(true)
+ verify(repository).setPrimaryShow(any(KeyguardBouncerModel::class.java))
+ verify(repository).setPrimaryShowingSoon(false)
+ }
+
+ @Test
+ fun testShow_isNotScrimmed() {
+ verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
+ }
+
+ @Test
+ fun testShow_keyguardIsDone() {
+ `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+ verify(keyguardStateController, never()).notifyBouncerShowing(true)
+ verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+ }
+
+ @Test
+ fun testHide() {
+ mPrimaryBouncerInteractor.hide()
+ verify(falsingCollector).onBouncerHidden()
+ verify(keyguardStateController).notifyBouncerShowing(false)
+ verify(repository).setPrimaryShowingSoon(false)
+ verify(repository).setPrimaryVisible(false)
+ verify(repository).setPrimaryHide(true)
+ verify(repository).setPrimaryShow(null)
+ }
+
+ @Test
+ fun testExpansion() {
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
+ verify(repository).setPanelExpansion(0.6f)
+ verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+ }
+
+ @Test
+ fun testExpansion_fullyShown() {
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
+ verify(falsingCollector).onBouncerShown()
+ verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
+ }
+
+ @Test
+ fun testExpansion_fullyHidden() {
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
+ verify(repository).setPrimaryVisible(false)
+ verify(repository).setPrimaryShow(null)
+ verify(repository).setPrimaryHide(true)
+ verify(falsingCollector).onBouncerHidden()
+ verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
+ verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
+ }
+
+ @Test
+ fun testExpansion_startingToHide() {
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
+ verify(repository).setPrimaryStartingToHide(true)
+ verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
+ }
+
+ @Test
+ fun testShowMessage() {
+ val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
+ mPrimaryBouncerInteractor.showMessage("abc", null)
+ verify(repository).setShowMessage(argCaptor.capture())
+ assertThat(argCaptor.value.message).isEqualTo("abc")
+ }
+
+ @Test
+ fun testDismissAction() {
+ val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
+ val cancelAction = mock(Runnable::class.java)
+ mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+ verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
+ }
+
+ @Test
+ fun testUpdateResources() {
+ mPrimaryBouncerInteractor.updateResources()
+ verify(repository).setResourceUpdateRequests(true)
+ }
+
+ @Test
+ fun testNotifyKeyguardAuthenticated() {
+ mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
+ verify(repository).setKeyguardAuthenticated(true)
+ }
+
+ @Test
+ fun testNotifyShowedMessage() {
+ mPrimaryBouncerInteractor.onMessageShown()
+ verify(repository).setShowMessage(null)
+ }
+
+ @Test
+ fun testOnScreenTurnedOff() {
+ mPrimaryBouncerInteractor.onScreenTurnedOff()
+ verify(repository).setOnScreenTurnedOff(true)
+ }
+
+ @Test
+ fun testSetKeyguardPosition() {
+ mPrimaryBouncerInteractor.setKeyguardPosition(0f)
+ verify(repository).setKeyguardPosition(0f)
+ }
+
+ @Test
+ fun testNotifyKeyguardAuthenticatedHandled() {
+ mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
+ verify(repository).setKeyguardAuthenticated(null)
+ }
+
+ @Test
+ fun testNotifyUpdatedResources() {
+ mPrimaryBouncerInteractor.notifyUpdatedResources()
+ verify(repository).setResourceUpdateRequests(false)
+ }
+
+ @Test
+ fun testSetBackButtonEnabled() {
+ mPrimaryBouncerInteractor.setBackButtonEnabled(true)
+ verify(repository).setIsBackButtonEnabled(true)
+ }
+
+ @Test
+ fun testStartDisappearAnimation() {
+ val runnable = mock(Runnable::class.java)
+ mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
+ verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+ }
+
+ @Test
+ fun testIsFullShowing() {
+ `when`(repository.primaryBouncerVisible.value).thenReturn(true)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
+ `when`(repository.primaryBouncerVisible.value).thenReturn(false)
+ assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
+ }
+
+ @Test
+ fun testIsScrimmed() {
+ `when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
+ assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
+ `when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
+ assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
+ }
+
+ @Test
+ fun testIsInTransit() {
+ `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+ assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+ `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+ assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+ }
+
+ @Test
+ fun testIsAnimatingAway() {
+ `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+ assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
+ `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
+ }
+
+ @Test
+ fun testWillDismissWithAction() {
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
+ assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
+ assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 78148c4..ecc63ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
@@ -38,10 +39,13 @@
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
@@ -56,6 +60,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
@@ -115,11 +120,35 @@
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs =
setOf(
homeControlsQuickAffordanceConfig,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
new file mode 100644
index 0000000..3727134
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBouncerViewModelTest : SysuiTestCase() {
+ lateinit var underTest: KeyguardBouncerViewModel
+ @Mock lateinit var bouncerView: BouncerView
+ @Mock lateinit var bouncerInteractor: PrimaryBouncerInteractor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+ }
+
+ @Test
+ fun setMessage() =
+ runBlocking(Dispatchers.Main.immediate) {
+ val flow = MutableStateFlow<BouncerShowMessageModel?>(null)
+ var message: BouncerShowMessageModel? = null
+ Mockito.`when`(bouncerInteractor.showMessage)
+ .thenReturn(flow as Flow<BouncerShowMessageModel>)
+ // Reinitialize the view model.
+ underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+
+ flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null)
+
+ val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+ assertThat(message?.message).isEqualTo("abc")
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 7cd8e74..56c91bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -42,6 +42,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -464,7 +465,7 @@
fun onFalseTapOrTouch() {
whenever(mockController.getTransportControls()).thenReturn(mockTransport)
whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
- whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
viewModel.updateController(mockController)
val pos = 169
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 575b1c6..9d33e6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -22,13 +22,13 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.ui.MediaPlayerData
import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -64,7 +64,7 @@
class MediaDataFilterTest : SysuiTestCase() {
@Mock private lateinit var listener: MediaDataManager.Listener
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@@ -85,7 +85,7 @@
mediaDataFilter =
MediaDataFilter(
context,
- broadcastDispatcher,
+ userTracker,
broadcastSender,
lockscreenUserManager,
executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 11eb26b..52b694f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -20,6 +20,8 @@
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceTarget
import android.content.Intent
import android.graphics.Bitmap
@@ -60,7 +62,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -106,6 +107,7 @@
lateinit var metadataBuilder: MediaMetadata.Builder
lateinit var backgroundExecutor: FakeExecutor
lateinit var foregroundExecutor: FakeExecutor
+ lateinit var uiExecutor: FakeExecutor
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
@@ -117,6 +119,7 @@
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
@Mock lateinit var activityStarter: ActivityStarter
+ @Mock lateinit var smartspaceManager: SmartspaceManager
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -131,6 +134,7 @@
@Mock private lateinit var tunerService: TunerService
@Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
@Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+ @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -145,6 +149,7 @@
fun setup() {
foregroundExecutor = FakeExecutor(clock)
backgroundExecutor = FakeExecutor(clock)
+ uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
Settings.Secure.putInt(
context.contentResolver,
@@ -155,6 +160,7 @@
MediaDataManager(
context = context,
backgroundExecutor = backgroundExecutor,
+ uiExecutor = uiExecutor,
foregroundExecutor = foregroundExecutor,
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
@@ -172,7 +178,8 @@
systemClock = clock,
tunerService = tunerService,
mediaFlags = mediaFlags,
- logger = logger
+ logger = logger,
+ smartspaceManager = smartspaceManager,
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -191,6 +198,7 @@
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
+ verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
@@ -767,15 +775,14 @@
.onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
- @Ignore("b/233283726")
@Test
fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
verify(logger).getNewInstanceId()
smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- foregroundExecutor.advanceClockToLast()
- foregroundExecutor.runAllReady()
+ uiExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
verifyNoMoreInteractions(logger)
@@ -798,7 +805,6 @@
.onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
- @Ignore("b/229838140")
@Test
fun testMediaRecommendationDisabled_removesSmartspaceData() {
// GIVEN a media recommendation card is present
@@ -815,7 +821,9 @@
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
// THEN listeners are notified
+ uiExecutor.advanceClockToLast()
foregroundExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
foregroundExecutor.runAllReady()
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index a8f4138..a943746 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -25,7 +25,8 @@
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -62,6 +63,7 @@
@Mock private lateinit var mediaViewHolder: MediaViewHolder
@Mock private lateinit var gutsViewHolder: GutsViewHolder
@Mock private lateinit var multiRippleController: MultiRippleController
+ @Mock private lateinit var turbulenceNoiseController: TurbulenceNoiseController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
@@ -76,6 +78,7 @@
context,
mediaViewHolder,
multiRippleController,
+ turbulenceNoiseController,
animatingColorTransitionFactory
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 8190156..761773b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -78,9 +78,10 @@
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.ripple.MultiRippleView
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -178,6 +179,7 @@
private lateinit var dismiss: FrameLayout
private lateinit var dismissText: TextView
private lateinit var multiRippleView: MultiRippleView
+ private lateinit var turbulenceNoiseView: TurbulenceNoiseView
private lateinit var session: MediaSession
private lateinit var device: MediaDeviceData
@@ -210,7 +212,10 @@
private lateinit var recSubtitle3: TextView
private var shouldShowBroadcastButton: Boolean = false
private val fakeFeatureFlag =
- FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) }
+ FakeFeatureFlags().apply {
+ this.set(Flags.UMO_SURFACE_RIPPLE, false)
+ this.set(Flags.MEDIA_FALSING_PENALTY, true)
+ }
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -382,6 +387,7 @@
}
multiRippleView = MultiRippleView(context, null)
+ turbulenceNoiseView = TurbulenceNoiseView(context, null)
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
@@ -425,6 +431,7 @@
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+ whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
}
/** Initialize elements for the recommendation view holder */
@@ -1137,6 +1144,19 @@
/* ***** Guts tests for the player ***** */
@Test
+ fun player_longClick_isFalse() {
+ whenever(falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+ player.attachPlayer(viewHolder)
+
+ val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+ verify(viewHolder.player).onLongClickListener = captor.capture()
+
+ captor.value.onLongClick(viewHolder.player)
+ verify(mediaViewController, never()).openGuts()
+ verify(mediaViewController, never()).closeGuts()
+ }
+
+ @Test
fun player_longClickWhenGutsClosed_gutsOpens() {
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, KEY)
@@ -1316,6 +1336,20 @@
/* ***** Guts tests for the recommendations ***** */
@Test
+ fun recommendations_longClick_isFalse() {
+ whenever(falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(smartspaceData)
+
+ val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+ verify(viewHolder.player).onLongClickListener = captor.capture()
+
+ captor.value.onLongClick(viewHolder.player)
+ verify(mediaViewController, never()).openGuts()
+ verify(mediaViewController, never()).closeGuts()
+ }
+
+ @Test
fun recommendations_longClickWhenGutsClosed_gutsOpens() {
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(smartspaceData)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
new file mode 100644
index 0000000..771b986
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.media.dialog;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.media.MediaOutputConstants;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaOutputDialogReceiverTest extends SysuiTestCase {
+
+ private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
+
+ private final MediaOutputDialogFactory mMockMediaOutputDialogFactory =
+ mock(MediaOutputDialogFactory.class);
+
+ private final MediaOutputBroadcastDialogFactory mMockMediaOutputBroadcastDialogFactory =
+ mock(MediaOutputBroadcastDialogFactory.class);
+
+ @Before
+ public void setup() {
+ mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogFactory,
+ mMockMediaOutputBroadcastDialogFactory);
+ }
+
+ @Test
+ public void showOutputSwitcher_ExtraPackageName_DialogFactoryCalled() {
+ Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, times(1))
+ .create(getContext().getPackageName(), false, null);
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void showOutputSwitcher_WrongExtraKey_DialogFactoryNotCalled() {
+ Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
+ intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void showOutputSwitcher_NoExtra_DialogFactoryNotCalled() {
+ Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void launchMediaOutputDialog_ExtraPackageName_DialogFactoryCalled() {
+ Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
+ intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, times(1))
+ .create(getContext().getPackageName(), false, null);
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void launchMediaOutputDialog_WrongExtraKey_DialogFactoryNotCalled() {
+ Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
+ intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void launchMediaOutputDialog_NoExtra_DialogFactoryNotCalled() {
+ Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() {
+ Intent intent = new Intent(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+ intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, times(1))
+ .create(getContext().getPackageName(), false, null);
+ }
+
+ @Test
+ public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() {
+ Intent intent = new Intent(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+ intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() {
+ Intent intent = new Intent(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void unKnownAction_ExtraPackageName_FactoriesNotCalled() {
+ Intent intent = new Intent("UnKnown Action");
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+
+ @Test
+ public void unKnownActionAnd_NoExtra_FactoriesNotCalled() {
+ Intent intent = new Intent("UnKnown Action");
+ mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+ verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
new file mode 100644
index 0000000..4aa982e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.media.taptotransfer.receiver
+
+import android.content.Context
+import android.os.Handler
+import android.os.PowerManager
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
+
+class FakeMediaTttChipControllerReceiver(
+ commandQueue: CommandQueue,
+ context: Context,
+ logger: MediaTttLogger,
+ windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ mainHandler: Handler,
+ mediaTttFlags: MediaTttFlags,
+ uiEventLogger: MediaTttReceiverUiEventLogger,
+ viewUtil: ViewUtil,
+ wakeLockBuilder: WakeLock.Builder,
+) :
+ MediaTttChipControllerReceiver(
+ commandQueue,
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ mainHandler,
+ mediaTttFlags,
+ uiEventLogger,
+ viewUtil,
+ wakeLockBuilder,
+ ) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ // Just bypass the animation in tests
+ onAnimationEnd.run()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 68a5f47..23f7cdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -114,7 +114,7 @@
fakeWakeLockBuilder = WakeLockFake.Builder(context)
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
- controllerReceiver = MediaTttChipControllerReceiver(
+ controllerReceiver = FakeMediaTttChipControllerReceiver(
commandQueue,
context,
logger,
@@ -261,7 +261,12 @@
@Test
fun updateView_noOverrides_usesInfoFromAppIcon() {
controllerReceiver.displayView(
- ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null)
+ ChipReceiverInfo(
+ routeInfo,
+ appIconDrawableOverride = null,
+ appNameOverride = null,
+ id = "id",
+ )
)
val view = getChipView()
@@ -274,7 +279,12 @@
val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!!
controllerReceiver.displayView(
- ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null)
+ ChipReceiverInfo(
+ routeInfo,
+ drawableOverride,
+ appNameOverride = null,
+ id = "id",
+ )
)
val view = getChipView()
@@ -286,7 +296,12 @@
val appNameOverride = "Sweet New App"
controllerReceiver.displayView(
- ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride)
+ ChipReceiverInfo(
+ routeInfo,
+ appIconDrawableOverride = null,
+ appNameOverride,
+ id = "id",
+ )
)
val view = getChipView()
@@ -340,7 +355,7 @@
.addFeature("feature")
.setClientPackageName(packageName)
.build()
- return ChipReceiverInfo(routeInfo, null, null)
+ return ChipReceiverInfo(routeInfo, null, null, id = "id")
}
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
new file mode 100644
index 0000000..9b346d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.mediaprojection.appselector.data
+
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.IconFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.system.PackageManagerWrapper
+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.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
+
+ private val iconFactory: IconFactory = mock()
+ private val packageManagerWrapper: PackageManagerWrapper = mock()
+ private val packageManager: PackageManager = mock()
+ private val dispatcher = Dispatchers.Unconfined
+
+ private val appIconLoader =
+ IconLoaderLibAppIconLoader(
+ backgroundDispatcher = dispatcher,
+ context = context,
+ packageManagerWrapper = packageManagerWrapper,
+ packageManager = packageManager,
+ iconFactoryProvider = { iconFactory }
+ )
+
+ @Test
+ fun loadIcon_loadsIconUsingTheSameUserId() {
+ val icon = createIcon()
+ val component = ComponentName("com.test", "TestApplication")
+ givenIcon(component, userId = 123, icon = icon)
+
+ val loadedIcon = runBlocking { appIconLoader.loadIcon(userId = 123, component = component) }
+
+ assertThat(loadedIcon).isEqualTo(icon)
+ }
+
+ private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) {
+ val activityInfo = mock<ActivityInfo>()
+ whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo)
+ val rawIcon = mock<Drawable>()
+ whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon)
+
+ val bitmapInfo = mock<BitmapInfo>()
+ whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo)
+ whenever(bitmapInfo.newIcon(context)).thenReturn(icon)
+ }
+
+ private fun createIcon(): FastBitmapDrawable =
+ FastBitmapDrawable(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 939af16..d35a212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -4,6 +4,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -11,11 +12,11 @@
import com.android.wm.shell.util.GroupedRecentTaskInfo
import com.google.common.truth.Truth.assertThat
import java.util.*
+import java.util.function.Consumer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.function.Consumer
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -23,8 +24,14 @@
private val dispatcher = Dispatchers.Unconfined
private val recentTasks: RecentTasks = mock()
+ private val userTracker: UserTracker = mock()
private val recentTaskListProvider =
- ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+ ShellRecentTaskListProvider(
+ dispatcher,
+ Runnable::run,
+ Optional.of(recentTasks),
+ userTracker
+ )
@Test
fun loadRecentTasks_oneTask_returnsTheSameTask() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index 4c72406..3620233 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -19,6 +19,7 @@
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -66,6 +67,8 @@
private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock
private lateinit var safetyCenterManager: SafetyCenterManager
+ @Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
private val uiExecutor = FakeExecutor(FakeSystemClock())
private val backgroundExecutor = FakeExecutor(FakeSystemClock())
@@ -80,6 +83,7 @@
whenever(privacyChip.context).thenReturn(context)
whenever(privacyChip.resources).thenReturn(context.resources)
whenever(privacyChip.isAttachedToWindow).thenReturn(true)
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
cameraSlotName = context.getString(com.android.internal.R.string.status_bar_camera)
microphoneSlotName = context.getString(com.android.internal.R.string.status_bar_microphone)
@@ -98,7 +102,8 @@
activityStarter,
appOpsController,
broadcastDispatcher,
- safetyCenterManager
+ safetyCenterManager,
+ deviceProvisionedController
)
backgroundExecutor.runAllReady()
@@ -199,6 +204,18 @@
)
}
+ @Test
+ fun testNoDialogWhenDeviceNotProvisioned() {
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
+ controller.onParentVisible()
+
+ val captor = argumentCaptor<View.OnClickListener>()
+ verify(privacyChip).setOnClickListener(capture(captor))
+
+ captor.value.onClick(privacyChip)
+ verify(privacyDialogController, never()).showDialog(any(Context::class.java))
+ }
+
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera)
whenever(privacyItemController.locationAvailable).thenReturn(location)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index cd7a949..72e022e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -439,6 +439,17 @@
verify(mQSPanelController).setExpanded(false);
}
+ @Test
+ public void startsListeningAfterStateChangeToExpanded_inSplitShade() {
+ QSFragment fragment = resumeAndGetFragment();
+ enableSplitShade();
+ fragment.setQsVisible(true);
+ clearInvocations(mQSPanelController);
+
+ fragment.setExpanded(true);
+ verify(mQSPanelController).setListening(true, true);
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 3c867ab..9f28708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -64,7 +64,7 @@
whenever(brightnessSliderFactory.create(any(), any())).thenReturn(brightnessSlider)
whenever(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
whenever(qsPanel.resources).thenReturn(mContext.orCreateTestableResources.resources)
- whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false)
+ whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
whenever(qsPanel.setListening(anyBoolean())).then {
whenever(qsPanel.isListening).thenReturn(it.getArgument(0))
}
@@ -116,9 +116,9 @@
@Test
fun testIsBouncerInTransit() {
- whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true)
+ whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true)
assertThat(controller.isBouncerInTransit()).isEqualTo(true)
- whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false)
+ whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
assertThat(controller.isBouncerInTransit()).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index c452872..fb1a720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -54,6 +54,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -66,7 +67,6 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 5abc0e1..35c8cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -27,6 +27,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.Context;
+import android.content.res.Resources;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -42,16 +44,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileLayoutTest extends SysuiTestCase {
- private TileLayout mTileLayout;
+ private Resources mResources;
private int mLayoutSizeForOneTile;
+ private TileLayout mTileLayout; // under test
@Before
public void setUp() throws Exception {
- mTileLayout = new TileLayout(mContext);
+ Context context = Mockito.spy(mContext);
+ mResources = Mockito.spy(context.getResources());
+ Mockito.when(mContext.getResources()).thenReturn(mResources);
+
+ mTileLayout = new TileLayout(context);
// Layout needs to leave space for the tile margins. Three times the margin size is
// sufficient for any number of columns.
mLayoutSizeForOneTile =
@@ -203,4 +211,21 @@
verify(tileRecord1.tileView).setPosition(0);
verify(tileRecord2.tileView).setPosition(1);
}
+
+ @Test
+ public void resourcesChanged_updateResources_returnsTrue() {
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+ mTileLayout.updateResources(); // setup with 1
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+
+ assertEquals(true, mTileLayout.updateResources());
+ }
+
+ @Test
+ public void resourcesSame_updateResources_returnsFalse() {
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+ mTileLayout.updateResources(); // setup with 1
+
+ assertEquals(false, mTileLayout.updateResources());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 213eca8..25c95ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -42,12 +42,12 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 2c2ddbb..645b1cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -16,10 +16,8 @@
package com.android.systemui.qs.footer.domain.interactor
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.os.UserHandle
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -30,17 +28,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.FooterActionsTestUtils
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.truth.correspondence.FakeUiEvent
import com.android.systemui.truth.correspondence.LogMaker
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -156,54 +150,4 @@
// We only unlock the device.
verify(activityStarter).postQSRunnableDismissingKeyguard(any())
}
-
- @Test
- fun showUserSwitcher_fullScreenDisabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
- val userSwitchDialogController = mock<UserSwitchDialogController>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- userSwitchDialogController = userSwitchDialogController,
- )
-
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- verify(userSwitchDialogController).showDialog(context, expandable)
- }
-
- @Test
- fun showUserSwitcher_fullScreenEnabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
- val activityStarter = mock<ActivityStarter>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- activityStarter = activityStarter,
- )
-
- // The clicked expandable.
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- val intentCaptor = argumentCaptor<Intent>()
- verify(activityStarter)
- .startActivity(
- intentCaptor.capture(),
- /* dismissShade= */ eq(true),
- /* ActivityLaunchAnimator.Controller= */ nullable(),
- /* showOverLockscreenWhenLocked= */ eq(true),
- eq(UserHandle.SYSTEM),
- )
- assertThat(intentCaptor.value.component)
- .isEqualTo(
- ComponentName(
- context,
- UserSwitcherActivity::class.java,
- )
- )
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index b067ee7..f55d262 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -40,6 +40,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
@RunWith(AndroidTestingRunner.class)
@UiThreadTest
@@ -138,7 +140,7 @@
}
@Test
- public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+ public void testIconStartedAndStoppedWhenAllowAnimationsFalse() {
ImageView iv = new ImageView(mContext);
AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
State s = new State();
@@ -148,7 +150,9 @@
mIconView.updateIcon(iv, s, false);
- verify(d, never()).start();
+ InOrder inOrder = Mockito.inOrder(d);
+ inOrder.verify(d).start();
+ inOrder.verify(d).stop();
}
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 73a0cbc..030c59f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
@@ -64,6 +65,8 @@
private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
@Mock
private lateinit var mGlobalSettings: GlobalSettings
+ @Mock
+ private lateinit var mUserTracker: UserTracker
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
@@ -87,7 +90,8 @@
mQsLogger,
mBroadcastDispatcher,
mConnectivityManager,
- mGlobalSettings)
+ mGlobalSettings,
+ mUserTracker)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index b652aee..cac90a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -119,6 +119,6 @@
when(mController.isEnabledForQuickSettings()).thenReturn(true);
QSTile.State state = new QSTile.State();
mTile.handleUpdateState(state, null);
- assertEquals(state.state, Tile.STATE_ACTIVE);
+ assertEquals(state.state, Tile.STATE_INACTIVE);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3131f60..08a90b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -42,27 +42,21 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UserDetailViewAdapterTest : SysuiTestCase() {
- @Mock
- private lateinit var mUserSwitcherController: UserSwitcherController
- @Mock
- private lateinit var mParent: ViewGroup
- @Mock
- private lateinit var mUserDetailItemView: UserDetailItemView
- @Mock
- private lateinit var mOtherView: View
- @Mock
- private lateinit var mInflatedUserDetailItemView: UserDetailItemView
- @Mock
- private lateinit var mLayoutInflater: LayoutInflater
+ @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+ @Mock private lateinit var mParent: ViewGroup
+ @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+ @Mock private lateinit var mOtherView: View
+ @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+ @Mock private lateinit var mLayoutInflater: LayoutInflater
private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
private lateinit var adapter: UserDetailView.Adapter
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -77,10 +71,13 @@
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
.thenReturn(mInflatedUserDetailItemView)
`when`(mParent.context).thenReturn(mContext)
- adapter = UserDetailView.Adapter(
- mContext, mUserSwitcherController, uiEventLogger,
- falsingManagerFake
- )
+ adapter =
+ UserDetailView.Adapter(
+ mContext,
+ mUserSwitcherController,
+ uiEventLogger,
+ falsingManagerFake
+ )
mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 2ef7312..48a53bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -60,8 +60,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.UnreleasedFlag;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -169,8 +169,8 @@
private WifiStateWorker mWifiStateWorker;
@Mock
private SignalStrength mSignalStrength;
- @Mock
- private FeatureFlags mFlags;
+
+ private FakeFeatureFlags mFlags = new FakeFeatureFlags();
private TestableResources mTestableResources;
private InternetDialogController mInternetDialogController;
@@ -221,6 +221,7 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mInternetDialogController.mActivityStarter = mActivityStarter;
mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, false);
}
@After
@@ -410,7 +411,7 @@
@Test
public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
fakeAirplaneModeEnabled(false);
when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
@@ -767,7 +768,7 @@
@Test
public void getSignalStrengthIcon_differentSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false);
Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false);
@@ -777,7 +778,7 @@
@Test
public void getActiveAutoSwitchNonDdsSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
// active on non-DDS
SubscriptionInfo info = mock(SubscriptionInfo.class);
doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -813,7 +814,7 @@
@Test
public void getMobileNetworkSummary() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
doReturn(true).when(spyController).isMobileDataEnabled();
@@ -837,7 +838,7 @@
@Test
public void launchMobileNetworkSettings_validSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
spyController.launchMobileNetworkSettings(mDialogLaunchView);
@@ -848,7 +849,7 @@
@Test
public void launchMobileNetworkSettings_invalidSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(spyController).getActiveAutoSwitchNonDdsSubId();
@@ -860,7 +861,7 @@
@Test
public void setAutoDataSwitchMobileDataPolicy() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true);
verify(mTelephonyManager).setMobileDataPolicyEnabled(eq(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
new file mode 100644
index 0000000..0aa3621
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.screenrecord
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.widget.Spinner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+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.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class ScreenRecordPermissionDialogTest : SysuiTestCase() {
+
+ @Mock private lateinit var starter: ActivityStarter
+ @Mock private lateinit var controller: RecordingController
+ @Mock private lateinit var userContextProvider: UserContextProvider
+ @Mock private lateinit var flags: FeatureFlags
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var onStartRecordingClicked: Runnable
+
+ private lateinit var dialog: ScreenRecordPermissionDialog
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ dialog =
+ ScreenRecordPermissionDialog(
+ context,
+ controller,
+ starter,
+ dialogLaunchAnimator,
+ userContextProvider,
+ onStartRecordingClicked
+ )
+ dialog.onCreate(null)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() {
+ dialog.show()
+
+ val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner).visibility
+ assertThat(visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testShowDialog_singleAppSelected_showTapsIsGone() {
+ dialog.show()
+ onSpinnerItemSelected(SINGLE_APP)
+
+ val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
+ assertThat(visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testShowDialog_entireScreenSelected_showTapsIsVisible() {
+ dialog.show()
+ onSpinnerItemSelected(ENTIRE_SCREEN)
+
+ val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
+ assertThat(visibility).isEqualTo(View.VISIBLE)
+ }
+
+ private fun onSpinnerItemSelected(position: Int) {
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index f4bc232..df3a62f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -75,7 +75,7 @@
private static final byte[] EXIF_FILE_TAG = "Exif\u0000\u0000".getBytes(US_ASCII);
private static final ZonedDateTime CAPTURE_TIME =
- ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST"));
+ ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("America/New_York"));
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 8c9404e..85c8ba7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -184,7 +184,7 @@
ActionTransition::new, mSmartActionsProvider);
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png")).get().action;
+ Uri.parse("Screenshot_123.png"), true).get().action;
Intent intent = shareAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -212,7 +212,7 @@
ActionTransition::new, mSmartActionsProvider);
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png")).get().action;
+ Uri.parse("Screenshot_123.png"), true).get().action;
Intent intent = editAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -241,7 +241,7 @@
Notification.Action deleteAction = task.createDeleteAction(mContext,
mContext.getResources(),
- Uri.parse("Screenshot_123.png"));
+ Uri.parse("Screenshot_123.png"), true);
Intent intent = deleteAction.actionIntent.getIntent();
assertNotNull(intent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
new file mode 100644
index 0000000..333e634
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.sensorprivacy
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SensorUseStartedActivityTest : SysuiTestCase() {
+ open class SensorUseStartedActivityTestable :
+ SensorUseStartedActivity(
+ sensorPrivacyController = mock(),
+ keyguardStateController = mock(),
+ keyguardDismissUtil = mock(),
+ bgHandler = mock(),
+ )
+
+ @get:Rule val activityRule = ActivityScenarioRule(SensorUseStartedActivityTestable::class.java)
+
+ @Test
+ fun onBackPressed_doNothing() {
+ activityRule.scenario.onActivity { activity ->
+ assertThat(activity.isFinishing).isFalse()
+
+ activity.onBackPressed()
+
+ assertThat(activity.isFinishing).isFalse()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
deleted file mode 100644
index 1b515c6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.settings;
-
-import android.content.Intent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Testing functionality of the current user tracker
- */
-@SmallTest
-public class CurrentUserTrackerTest extends SysuiTestCase {
-
- private CurrentUserTracker mTracker;
- private CurrentUserTracker.UserReceiver mReceiver;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mReceiver = new CurrentUserTracker.UserReceiver(mBroadcastDispatcher);
- mTracker = new CurrentUserTracker(mReceiver) {
- @Override
- public void onUserSwitched(int newUserId) {
- stopTracking();
- }
- };
- }
-
- @Test
- public void testBroadCastDoesntCrashOnConcurrentModification() {
- mTracker.startTracking();
- CurrentUserTracker secondTracker = new CurrentUserTracker(mReceiver) {
- @Override
- public void onUserSwitched(int newUserId) {
- stopTracking();
- }
- };
- secondTracker.startTracking();
- triggerUserSwitch();
- }
- /**
- * Simulates a user switch event.
- */
- private void triggerUserSwitch() {
- Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, 1);
- mReceiver.onReceive(getContext(), intent);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 1130bda..9d1802a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -28,9 +28,10 @@
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -45,7 +46,9 @@
@TestableLooper.RunWithLooper
class BrightnessDialogTest : SysuiTestCase() {
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
+ @Mock private lateinit var mainExecutor: Executor
@Mock private lateinit var backgroundHandler: Handler
@Mock private lateinit var brightnessSliderController: BrightnessSliderController
@@ -56,8 +59,9 @@
object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
override fun create(intent: Intent?): TestDialog {
return TestDialog(
- fakeBroadcastDispatcher,
+ userTracker,
brightnessSliderControllerFactory,
+ mainExecutor,
backgroundHandler
)
}
@@ -100,8 +104,15 @@
}
class TestDialog(
- broadcastDispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+ mainExecutor: Executor,
backgroundHandler: Handler
- ) : BrightnessDialog(broadcastDispatcher, brightnessSliderControllerFactory, backgroundHandler)
+ ) :
+ BrightnessDialog(
+ userTracker,
+ brightnessSliderControllerFactory,
+ mainExecutor,
+ backgroundHandler
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 0ce9056..2a3d32e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,7 +43,7 @@
load(context, context.resources.getXml(R.xml.qqs_header))
}
qsConstraint = ConstraintSet().apply {
- load(context, context.resources.getXml(R.xml.qs_header_new))
+ load(context, context.resources.getXml(R.xml.qs_header))
}
largeScreenConstraint = ConstraintSet().apply {
load(context, context.resources.getXml(R.xml.large_screen_shade_header))
@@ -320,6 +321,64 @@
assertThat(changes.largeScreenConstraintsChanges).isNull()
}
+ @Test
+ fun testRelevantViewsAreNotMatchConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ assertWithMessage("$name has 0 height in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ assertWithMessage("$name has 0 height in qs")
+ .that(qsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qs")
+ .that(qsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ }
+ }
+
+ @Test
+ fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ assertWithMessage("$name changes height")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight)
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight)
+ assertWithMessage("$name changes width")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth)
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth)
+ }
+ }
+
+ @Test
+ fun testEmptyCutoutDateIconsAreConstrainedWidth() {
+ CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
+
+ assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+ assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+ }
+
+ @Test
+ fun testCenterCutoutDateIconsAreConstrainedWidth() {
+ CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
+
+ assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+ assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+ }
+
private operator fun ConstraintsChanges.invoke() {
qqsConstraintsChanges?.invoke(qqsConstraint)
qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 14a3bc1..e1007fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -179,7 +179,6 @@
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
- whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
setUpDefaultInsets()
setUpMotionLayout(view)
@@ -212,7 +211,7 @@
assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
verify(qsConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
verify(largeScreenConstraints).load(eq(context), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index c98c1f2..69a4559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -94,7 +94,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.doze.DozeLog;
@@ -134,6 +133,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -199,6 +199,7 @@
@Mock private KeyguardBottomAreaView mQsFrame;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationShelfController mNotificationShelfController;
+ @Mock private NotificationGutsManager mGutsManager;
@Mock private KeyguardStatusBarView mKeyguardStatusBar;
@Mock private KeyguardUserSwitcherView mUserSwitcherView;
@Mock private ViewStub mUserSwitcherStubView;
@@ -308,7 +309,7 @@
MockitoAnnotations.initMocks(this);
SystemClock systemClock = new FakeSystemClock();
mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
- mInteractionJankMonitor);
+ mInteractionJankMonitor, mShadeExpansionStateManager);
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -379,7 +380,7 @@
mDumpManager,
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
- mInteractionJankMonitor),
+ mInteractionJankMonitor, mShadeExpansionStateManager),
mKeyguardBypassController,
mDozeParameters,
mScreenOffAnimationController);
@@ -454,6 +455,7 @@
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
+ mGutsManager,
mNotificationsQSContainerController,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
@@ -492,9 +494,9 @@
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
systemClock,
- mock(CameraGestureHelper.class),
mKeyguardBottomAreaViewModel,
- mKeyguardBottomAreaInteractor);
+ mKeyguardBottomAreaInteractor,
+ mDumpManager);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
@@ -755,6 +757,8 @@
@Test
public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+ mFalsingManager.setIsClassifierEnabled(true);
+ mFalsingManager.setIsFalseTouch(false);
// Start shade collapse with swipe up
onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
@@ -854,7 +858,7 @@
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
null);
- verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
}
@Test
@@ -864,7 +868,7 @@
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
null);
- verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
}
@Test
@@ -1120,6 +1124,19 @@
}
@Test
+ public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
+ enableSplitShade(true);
+ mStatusBarStateController.setState(SHADE);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ mStatusBarStateController.setState(KEYGUARD);
+
+
+ assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
public void testSwitchesToCorrectClockInSinglePaneShade() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 95cf9d6..d7d17b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -239,9 +239,9 @@
@Test
public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
- mNotificationShadeWindowController.setPanelExpanded(true);
+ mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.setPanelExpanded(true);
+ mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
verifyNoMoreInteractions(mWindowManager);
mNotificationShadeWindowController.setNotificationShadeFocusable(true);
@@ -313,7 +313,7 @@
verifyNoMoreInteractions(mWindowManager);
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
mNotificationShadeWindowController.setForceDozeBrightness(false);
verify(mWindowManager, never()).updateViewLayout(any(), any());
});
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 db7e017..c3207c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -94,6 +95,8 @@
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var notificationInsetsController: NotificationInsetsController
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerContainer: ViewGroup
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -124,6 +127,7 @@
centralSurfaces,
notificationShadeWindowController,
keyguardUnlockAnimationController,
+ notificationInsetsController,
ambientState,
pulsingGestureListener,
featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index a4a7995..4bf00c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -91,6 +92,7 @@
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+ @Mock private NotificationInsetsController mNotificationInsetsController;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -125,6 +127,7 @@
mCentralSurfaces,
mNotificationShadeWindowController,
mKeyguardUnlockAnimationController,
+ mNotificationInsetsController,
mAmbientState,
mPulsingGestureListener,
mFeatureFlags,
@@ -152,7 +155,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should intercept touch
@@ -165,7 +168,7 @@
// WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(false);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we shouldn't intercept touch
@@ -178,7 +181,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 70cbc64..4d7741ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,11 +28,12 @@
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
+import org.json.JSONException
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -238,4 +239,52 @@
pluginListener.onPluginDisconnected(plugin2)
assertEquals(1, changeCallCount)
}
+
+ @Test
+ fun jsonDeserialization_gotExpectedObject() {
+ val expected = ClockRegistry.ClockSetting("ID", 500)
+ val actual = ClockRegistry.ClockSetting.deserialize("""{
+ "clockId":"ID",
+ "_applied_timestamp":500
+ }""")
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun jsonDeserialization_noTimestamp_gotExpectedObject() {
+ val expected = ClockRegistry.ClockSetting("ID", null)
+ val actual = ClockRegistry.ClockSetting.deserialize("{\"clockId\":\"ID\"}")
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
+ val expected = ClockRegistry.ClockSetting("ID", null)
+ val actual = ClockRegistry.ClockSetting.deserialize("""{
+ "clockId":"ID",
+ "_applied_timestamp":null
+ }""")
+ assertEquals(expected, actual)
+ }
+
+ @Test(expected = JSONException::class)
+ fun jsonDeserialization_noId_threwException() {
+ val expected = ClockRegistry.ClockSetting("ID", 500)
+ val actual = ClockRegistry.ClockSetting.deserialize("{\"_applied_timestamp\":500}")
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun jsonSerialization_gotExpectedString() {
+ val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
+ val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", 500))
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun jsonSerialization_noTimestamp_gotExpectedString() {
+ val expected = "{\"clockId\":\"ID\"}"
+ val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", null))
+ assertEquals(expected, actual)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 539a54b..a7588dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -43,6 +43,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.notNull
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -139,12 +140,19 @@
}
@Test
- fun defaultClock_events_onFontSettingChanged() {
+ fun defaultSmallClock_events_onFontSettingChanged() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onFontSettingChanged()
+ clock.smallClock.events.onFontSettingChanged(100f)
- verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
- verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+ verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(100f))
+ }
+
+ @Test
+ fun defaultLargeClock_events_onFontSettingChanged() {
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ clock.largeClock.events.onFontSettingChanged(200f)
+
+ verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
verify(mockLargeClockView).setLayoutParams(any())
}
@@ -171,4 +179,12 @@
verify(mockSmallClockView, times(2)).refreshFormat()
verify(mockLargeClockView, times(2)).refreshFormat()
}
+
+ @Test
+ fun test_aodClock_always_whiteColor() {
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ clock.animations.doze(0.9f) // set AOD mode to active
+ clock.smallClock.events.onRegionDarknessChanged(true)
+ verify((clock.smallClock.view as AnimatableClockView), never()).animateAppearOnLockscreen()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index c658593..c8a392b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -32,12 +32,12 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -87,6 +87,8 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -271,7 +273,7 @@
mUserManager, mExecutor, mExecutor, mFalsingManager,
mAuthController, mLockPatternUtils, mScreenLifecycle,
mKeyguardBypassController, mAccessibilityManager,
- mFaceHelpMessageDeferral);
+ mFaceHelpMessageDeferral, mock(KeyguardLogger.class));
mController.init();
mController.setIndicationArea(mIndicationArea);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -828,31 +830,6 @@
}
@Test
- public void updateMonitor_listenerUpdatesIndication() {
- createController();
- String restingIndication = "Resting indication";
- reset(mKeyguardUpdateMonitor);
-
- mController.setVisible(true);
- verifyIndicationMessage(INDICATION_TYPE_USER_LOCKED,
- mContext.getString(com.android.internal.R.string.lockscreen_storage_locked));
-
- reset(mRotateTextViewController);
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
- when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
- mController.setRestingIndication(restingIndication);
- verifyHideIndication(INDICATION_TYPE_USER_LOCKED);
- verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
-
- reset(mRotateTextViewController);
- reset(mKeyguardUpdateMonitor);
- when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- mKeyguardStateControllerCallback.onUnlockedChanged();
- verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
- }
-
- @Test
public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
@@ -1067,7 +1044,8 @@
// GIVEN a trust granted message but trust isn't granted
final String trustGrantedMsg = "testing trust granted message";
- mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), trustGrantedMsg);
verifyHideIndication(INDICATION_TYPE_TRUST);
@@ -1091,7 +1069,8 @@
// WHEN the showTrustGranted method is called
final String trustGrantedMsg = "testing trust granted message";
- mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), trustGrantedMsg);
// THEN verify the trust granted message shows
verifyIndicationMessage(
@@ -1108,7 +1087,8 @@
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
// WHEN the showTrustGranted method is called with a null message
- mController.getKeyguardCallback().showTrustGrantedMessage(null);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), null);
// THEN verify the default trust granted message shows
verifyIndicationMessage(
@@ -1125,7 +1105,8 @@
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
// WHEN the showTrustGranted method is called with an EMPTY string
- mController.getKeyguardCallback().showTrustGrantedMessage("");
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), "");
// THEN verify NO trust message is shown
verifyNoMessage(INDICATION_TYPE_TRUST);
@@ -1421,6 +1402,21 @@
}
@Test
+ public void onBiometricError_faceLockedOutSecondTimeOnBouncer_showsUnavailableMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+
+ onFaceLockoutError("second lockout");
+
+ verify(mStatusBarKeyguardViewManager)
+ .setKeyguardMessage(
+ eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)),
+ any());
+ }
+
+ @Test
public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
createController();
onFaceLockoutError("first lockout");
@@ -1472,6 +1468,44 @@
}
@Test
+ public void onFpLockoutStateChanged_whenFpIsLockedOut_showsPersistentMessage() {
+ createController();
+ mController.setVisible(true);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+ verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onFpLockoutStateChanged_whenFpIsNotLockedOut_showsPersistentMessage() {
+ createController();
+ mController.setVisible(true);
+ clearInvocations(mRotateTextViewController);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+ verifyHideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
+ }
+
+ @Test
+ public void onVisibilityChange_showsPersistentMessage_ifFpIsLockedOut() {
+ createController();
+ mController.setVisible(false);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ clearInvocations(mRotateTextViewController);
+
+ mController.setVisible(true);
+
+ verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
createController();
onFaceLockoutError("first lockout");
@@ -1485,6 +1519,44 @@
mContext.getString(R.string.keyguard_face_unlock_unavailable));
}
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsNotPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ private void screenIsTurningOn() {
+ when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
+ }
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
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 5e11858..3412679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,7 +37,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
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 bdafa48..15a687d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -53,6 +53,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -78,6 +79,8 @@
private NotificationPresenter mPresenter;
@Mock
private UserManager mUserManager;
+ @Mock
+ private UserTracker mUserTracker;
// Dependency mocks:
@Mock
@@ -115,6 +118,7 @@
MockitoAnnotations.initMocks(this);
int currentUserId = ActivityManager.getCurrentUser();
+ when(mUserTracker.getUserId()).thenReturn(currentUserId);
mSettings = new FakeSettings();
mSettings.setUserId(ActivityManager.getCurrentUser());
mCurrentUser = new UserInfo(currentUserId, "", 0);
@@ -344,6 +348,7 @@
mBroadcastDispatcher,
mDevicePolicyManager,
mUserManager,
+ mUserTracker,
(() -> mVisibilityProvider),
(() -> mNotifCollection),
mClickNotifier,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 1d8e5de..5124eb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -48,6 +49,7 @@
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockDarkAnimator: ObjectAnimator
+ @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
private lateinit var controller: StatusBarStateControllerImpl
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -62,7 +64,7 @@
controller = object : StatusBarStateControllerImpl(
uiEventLogger,
mock(DumpManager::class.java),
- interactionJankMonitor
+ interactionJankMonitor, shadeExpansionStateManager
) {
override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
new file mode 100644
index 0000000..62b4e7b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.statusbar.connectivity
+
+import android.content.res.Resources
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+
+typealias CarrierId = Int
+
+typealias NetworkType = String
+
+typealias ResId = Int
+
+class MobileIconCarrierIdOverridesFake : MobileIconCarrierIdOverrides {
+ /** Backing for [carrierIdEntryExists] */
+ var overriddenIds = mutableSetOf<Int>()
+
+ /** Backing for [getOverrideFor]. Map should be Map< CarrierId < NetworkType, ResId>> */
+ var overridesByCarrierId = mutableMapOf<CarrierId, Map<NetworkType, ResId>>()
+
+ override fun getOverrideFor(
+ carrierId: CarrierId,
+ networkType: NetworkType,
+ resources: Resources
+ ): ResId {
+ if (!overriddenIds.contains(carrierId)) return 0
+
+ return overridesByCarrierId[carrierId]?.get(networkType) ?: 0
+ }
+
+ override fun carrierIdEntryExists(carrierId: Int): Boolean {
+ return overriddenIds.contains(carrierId)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
deleted file mode 100644
index 7ddfde3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.connectivity;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-
-import com.android.settingslib.mobile.TelephonyIcons;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class MobileStateTest extends SysuiTestCase {
-
- private final MobileState mState = new MobileState();
-
- @Before
- public void setUp() {
- }
-
- @Test
- public void testIsDataDisabledOrNotDefault_dataDisabled() {
- mState.iconGroup = TelephonyIcons.DATA_DISABLED;
- mState.userSetup = true;
-
- assertTrue(mState.isDataDisabledOrNotDefault());
- }
-
- @Test
- public void testIsDataDisabledOrNotDefault_notDefaultData() {
- mState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA;
- mState.userSetup = true;
-
- assertTrue(mState.isDataDisabledOrNotDefault());
- }
-
- @Test
- public void testIsDataDisabledOrNotDefault_notDisabled() {
- mState.iconGroup = TelephonyIcons.G;
- mState.userSetup = true;
-
- assertFalse(mState.isDataDisabledOrNotDefault());
- }
-
- @Test
- public void testHasActivityIn_noData_noActivity() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityIn = false;
-
- assertFalse(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityIn_noData_activityIn() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityIn = true;
-
- assertFalse(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityIn_dataConnected_activityIn() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = false;
- mState.activityIn = true;
-
- assertTrue(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityIn_carrierNetworkChange() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = true;
- mState.activityIn = true;
-
- assertFalse(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityOut_noData_noActivity() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityOut = false;
-
- assertFalse(mState.hasActivityOut());
- }
-
- @Test
- public void testHasActivityOut_noData_activityOut() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityOut = true;
-
- assertFalse(mState.hasActivityOut());
- }
-
- @Test
- public void testHasActivityOut_dataConnected_activityOut() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = false;
- mState.activityOut = true;
-
- assertTrue(mState.hasActivityOut());
- }
-
- @Test
- public void testHasActivityOut_carrierNetworkChange() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = true;
- mState.activityOut = true;
-
- assertFalse(mState.hasActivityOut());
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
new file mode 100644
index 0000000..a226ded
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.connectivity
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MobileStateTest : SysuiTestCase() {
+
+ private val state = MobileState()
+ @Before fun setUp() {}
+
+ @Test
+ fun testIsDataDisabledOrNotDefault_dataDisabled() {
+ state.iconGroup = TelephonyIcons.DATA_DISABLED
+ state.userSetup = true
+ assertTrue(state.isDataDisabledOrNotDefault)
+ }
+
+ @Test
+ fun testIsDataDisabledOrNotDefault_notDefaultData() {
+ state.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA
+ state.userSetup = true
+ assertTrue(state.isDataDisabledOrNotDefault)
+ }
+
+ @Test
+ fun testIsDataDisabledOrNotDefault_notDisabled() {
+ state.iconGroup = TelephonyIcons.G
+ state.userSetup = true
+ assertFalse(state.isDataDisabledOrNotDefault)
+ }
+
+ @Test
+ fun testHasActivityIn_noData_noActivity() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityIn = false
+ assertFalse(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityIn_noData_activityIn() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityIn = true
+ assertFalse(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityIn_dataConnected_activityIn() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = false
+ state.activityIn = true
+ assertTrue(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityIn_carrierNetworkChange() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = true
+ state.activityIn = true
+ assertFalse(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityOut_noData_noActivity() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityOut = false
+ assertFalse(state.hasActivityOut())
+ }
+
+ @Test
+ fun testHasActivityOut_noData_activityOut() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityOut = true
+ assertFalse(state.hasActivityOut())
+ }
+
+ @Test
+ fun testHasActivityOut_dataConnected_activityOut() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = false
+ state.activityOut = true
+ assertTrue(state.hasActivityOut())
+ }
+
+ @Test
+ fun testHasActivityOut_carrierNetworkChange() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = true
+ state.activityOut = true
+ assertFalse(state.hasActivityOut())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 9c65fac..faf4592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -71,6 +71,8 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -115,6 +117,7 @@
protected TelephonyManager mMockTm;
protected TelephonyListenerManager mTelephonyListenerManager;
protected BroadcastDispatcher mMockBd;
+ protected UserTracker mUserTracker;
protected Config mConfig;
protected CallbackHandler mCallbackHandler;
protected SubscriptionDefaults mMockSubDefaults;
@@ -125,6 +128,8 @@
protected CarrierConfigTracker mCarrierConfigTracker;
protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
protected Handler mMainHandler;
+ // Use a real mobile mappings object since lots of tests rely on it
+ protected FakeMobileMappingsProxy mMobileMappingsProxy = new FakeMobileMappingsProxy();
protected WifiStatusTrackerFactory mWifiStatusTrackerFactory;
protected MobileSignalControllerFactory mMobileFactory;
@@ -169,6 +174,7 @@
mMockSm = mock(SubscriptionManager.class);
mMockCm = mock(ConnectivityManager.class);
mMockBd = mock(BroadcastDispatcher.class);
+ mUserTracker = mock(UserTracker.class);
mMockNsm = mock(NetworkScoreManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mCarrierConfigTracker = mock(CarrierConfigTracker.class);
@@ -219,10 +225,13 @@
mWifiStatusTrackerFactory = new WifiStatusTrackerFactory(
mContext, mMockWm, mMockNsm, mMockCm, mMainHandler);
+ // Most of these tests rely on the actual MobileMappings behavior
+ mMobileMappingsProxy.setUseRealImpl(true);
mMobileFactory = new MobileSignalControllerFactory(
mContext,
mCallbackHandler,
- mCarrierConfigTracker
+ mCarrierConfigTracker,
+ mMobileMappingsProxy
);
mNetworkController = new NetworkControllerImpl(mContext,
@@ -240,6 +249,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 4bed4a1..ca75a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -18,12 +18,21 @@
import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED;
+import static android.telephony.TelephonyManager.EXTRA_CARRIER_ID;
+import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID;
+import static com.android.settingslib.mobile.TelephonyIcons.NR_5G_PLUS;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.Intent;
import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.Looper;
@@ -35,6 +44,7 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
@@ -45,6 +55,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -142,6 +154,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
@@ -329,6 +342,57 @@
assertFalse(mNetworkController.isMobileDataNetworkInService());
}
+ @Test
+ public void mobileSignalController_getsCarrierId() {
+ when(mMockTm.getSimCarrierId()).thenReturn(1);
+ setupDefaultSignal();
+
+ assertEquals(1, mMobileSignalController.getState().getCarrierId());
+ }
+
+ @Test
+ public void mobileSignalController_updatesCarrierId_onChange() {
+ when(mMockTm.getSimCarrierId()).thenReturn(1);
+ setupDefaultSignal();
+
+ // Updates are sent down through this broadcast, we can send the intent directly
+ Intent intent = new Intent(ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
+ intent.putExtra(EXTRA_SUBSCRIPTION_ID, mSubId);
+ intent.putExtra(EXTRA_CARRIER_ID, 2);
+
+ mMobileSignalController.handleBroadcast(intent);
+
+ assertEquals(2, mMobileSignalController.getState().getCarrierId());
+ }
+
+ @Test
+ public void networkTypeIcon_hasCarrierIdOverride() {
+ int fakeCarrier = 1;
+ int fakeIconOverride = 12345;
+ int testDataNetType = 100;
+ String testDataString = "100";
+ HashMap<String, MobileIconGroup> testMap = new HashMap<>();
+ testMap.put(testDataString, NR_5G_PLUS);
+
+ // Pretend that there is an override for this icon, and this carrier ID
+ NetworkTypeResIdCache mockCache = mock(NetworkTypeResIdCache.class);
+ when(mockCache.get(eq(NR_5G_PLUS), eq(fakeCarrier), any())).thenReturn(fakeIconOverride);
+
+ // Turn off the default mobile mapping, so we can override
+ mMobileMappingsProxy.setUseRealImpl(false);
+ mMobileMappingsProxy.setIconMap(testMap);
+ // Use the mocked cache
+ mMobileSignalController.mCurrentState.setNetworkTypeResIdCache(mockCache);
+ // Rebuild the network map
+ mMobileSignalController.setConfiguration(mConfig);
+ when(mMockTm.getSimCarrierId()).thenReturn(fakeCarrier);
+
+ setupDefaultSignal();
+ updateDataConnectionState(TelephonyManager.DATA_CONNECTED, testDataNetType);
+
+ verifyDataIndicators(fakeIconOverride);
+ }
+
private void testDataActivity(int direction, boolean in, boolean out) {
updateDataActivity(direction);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index d5f5105..84c242c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -82,6 +82,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
@@ -118,6 +119,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
@@ -152,6 +154,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
@@ -189,6 +192,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
@@ -274,6 +278,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
new file mode 100644
index 0000000..9e73487
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.statusbar.connectivity
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NetworkTypeResIdCacheTest : SysuiTestCase() {
+ private lateinit var cache: NetworkTypeResIdCache
+ private var overrides = MobileIconCarrierIdOverridesFake()
+
+ @Before
+ fun setUp() {
+ cache = NetworkTypeResIdCache(overrides)
+ }
+
+ @Test
+ fun carrier1_noOverride_usesDefault() {
+ assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconDefault1)
+ }
+
+ @Test
+ fun carrier1_overridden_usesOverride() {
+ overrides.overriddenIds.add(CARRIER_1)
+ overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+ assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconOverride1)
+ }
+
+ @Test
+ fun carrier1_override_carrier2UsesDefault() {
+ overrides.overriddenIds.add(CARRIER_1)
+ overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+ assertThat(cache.get(group1, CARRIER_2, context)).isEqualTo(iconDefault1)
+ }
+
+ @Test
+ fun carrier1_overrideType1_type2UsesDefault() {
+ overrides.overriddenIds.add(CARRIER_1)
+ overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+ assertThat(cache.get(group2, CARRIER_1, context)).isEqualTo(iconDefault2)
+ }
+
+ companion object {
+ // Simplified icon overrides here
+ const val CARRIER_1 = 1
+ const val CARRIER_2 = 2
+
+ const val NET_TYPE_1 = "one"
+ const val iconDefault1 = 123
+ const val iconOverride1 = 321
+ val group1 = MobileIconGroup(NET_TYPE_1, /* dataContentDesc */ 0, iconDefault1)
+
+ const val NET_TYPE_2 = "two"
+ const val iconDefault2 = 234
+
+ val group2 = MobileIconGroup(NET_TYPE_2, /* dataContentDesc*/ 0, iconDefault2)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 3b05321..94e3e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -741,22 +742,24 @@
@Test
public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
// GIVEN a collection with two grouped notifs in it
- CollectionEvent notif0 = postNotif(
+ CollectionEvent groupNotif = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- CollectionEvent notif1 = postNotif(
+ CollectionEvent childNotif = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
- NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
- NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry groupEntry = mCollectionListener.getEntry(groupNotif.key);
+ NotificationEntry childEntry = mCollectionListener.getEntry(childNotif.key);
+ ExpandableNotificationRow childRow = mock(ExpandableNotificationRow.class);
+ childEntry.setRow(childRow);
// WHEN the summary is dismissed
- mCollection.dismissNotification(entry0, defaultStats(entry0));
+ mCollection.dismissNotification(groupEntry, defaultStats(groupEntry));
// THEN all members of the group are marked as dismissed locally
- assertEquals(DISMISSED, entry0.getDismissState());
- assertEquals(PARENT_DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, groupEntry.getDismissState());
+ assertEquals(PARENT_DISMISSED, childEntry.getDismissState());
}
@Test
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 7e2e6f6..bdedd24 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
@@ -13,57 +13,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.collection.coordinator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
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.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
-import java.util.function.Consumer
-import org.junit.Before
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+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.clearInvocations
import org.mockito.Mockito.verify
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class KeyguardCoordinatorTest : SysuiTestCase() {
- private val notifPipeline: NotifPipeline = mock()
+
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val notifPipelineFlags: NotifPipelineFlags = mock()
+ private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
- private lateinit var onStateChangeListener: Consumer<String>
- private lateinit var keyguardFilter: NotifFilter
-
- @Before
- fun setup() {
- val keyguardCoordinator = KeyguardCoordinator(
- keyguardNotifVisibilityProvider,
- sectionHeaderVisibilityProvider,
- statusBarStateController
- )
- keyguardCoordinator.attach(notifPipeline)
- onStateChangeListener = withArgCaptor {
- verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
- }
- keyguardFilter = withArgCaptor {
- verify(notifPipeline).addFinalizeFilter(capture())
- }
- }
-
@Test
- fun testSetSectionHeadersVisibleInShade() {
+ fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
onStateChangeListener.accept("state change")
@@ -71,10 +69,176 @@
}
@Test
- fun testSetSectionHeadersNotVisibleOnKeyguard() {
+ fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
onStateChangeListener.accept("state change")
verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
}
+
+ @Test
+ fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: The keyguard is now showing
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN: The keyguard goes away
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is shown regardless
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenFilterAllowsNewNotif() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, no notifications present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // THEN: The notification is recognized as "unseen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenFilterSeenGroupSummaryWithUnseenChild() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeSummary = NotificationEntryBuilder().build()
+ val fakeChild = NotificationEntryBuilder()
+ .setGroup(context, "group")
+ .setGroupSummary(context, false)
+ .build()
+ GroupEntryBuilder()
+ .setSummary(fakeSummary)
+ .addChild(fakeChild)
+ .build()
+
+ collectionListener.onEntryAdded(fakeSummary)
+ collectionListener.onEntryAdded(fakeChild)
+
+ // WHEN: Keyguard is now showing, both notifications are marked as seen
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // WHEN: The child notification is now unseen
+ collectionListener.onEntryUpdated(fakeChild)
+
+ // THEN: The summary is not filtered out, because the child is unseen
+ assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for 5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is now recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for <5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is not recognized as "seen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ private fun runKeyguardCoordinatorTest(
+ testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
+ ) {
+ val testScope = TestScope(UnconfinedTestDispatcher())
+ val keyguardCoordinator =
+ KeyguardCoordinator(
+ keyguardNotifVisibilityProvider,
+ keyguardRepository,
+ notifPipelineFlags,
+ testScope.backgroundScope,
+ sectionHeaderVisibilityProvider,
+ statusBarStateController,
+ )
+ keyguardCoordinator.attach(notifPipeline)
+ KeyguardCoordinatorTestScope(keyguardCoordinator, testScope).run {
+ testScheduler.advanceUntilIdle()
+ testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { testBlock() }
+ }
+ }
+
+ private inner class KeyguardCoordinatorTestScope(
+ private val keyguardCoordinator: KeyguardCoordinator,
+ private val scope: TestScope,
+ ) : CoroutineScope by scope {
+ val testScheduler: TestCoroutineScheduler
+ get() = scope.testScheduler
+
+ val onStateChangeListener: Consumer<String> =
+ withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
+
+ val unseenFilter: NotifFilter
+ get() = keyguardCoordinator.unseenNotifFilter
+
+ // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
+ // removed
+ val collectionListener: NotifCollectionListener by lazy {
+ withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index b4a5f5c..e488f39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -38,6 +40,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -73,6 +76,7 @@
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private ShadeStateEvents mShadeStateEvents;
+ @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@@ -100,6 +104,7 @@
mHeadsUpManager,
mShadeStateEvents,
mStatusBarStateController,
+ mVisibilityLocationProvider,
mVisualStabilityProvider,
mWakefulnessLifecycle);
@@ -355,6 +360,38 @@
}
@Test
+ public void testMovingVisibleHeadsUpNotAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting and visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(true);
+
+ // VERIFY the notification cannot be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isFalse();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isFalse();
+ }
+
+ @Test
+ public void testMovingInvisibleHeadsUpAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting but not visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(false);
+
+ // VERIFY the notification can be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isTrue();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isTrue();
+ }
+
+ @Test
public void testNeverSuppressedChanges_noInvalidationCalled() {
// GIVEN no notifications are currently being suppressed from grouping nor being sorted
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
index 15cf17d..6167b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.testing.AndroidTestingRunner
import android.view.View
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,6 +27,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.matches
+import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -124,6 +129,64 @@
Assert.assertNull(controller3.view.parent)
Assert.assertNull(controller4.view.parent)
Assert.assertNull(controller5.view.parent)
+ verifyDetachingChildLogged(controller3, oldParent = controller2)
+ verifyDetachingChildLogged(controller4, oldParent = controller2)
+ verifyDetachingChildLogged(controller5, oldParent = controller2)
+ }
+
+ @Test
+ fun testRemovedGroupsWithKeepInParentAreKeptTogether() {
+ // GIVEN a preexisting tree with a group
+ // AND the group children supports keepInParent
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller5))
+ )
+ controller3.supportsKeepInParent = true
+ controller4.supportsKeepInParent = true
+ controller5.supportsKeepInParent = true
+
+ // WHEN the new spec removes the entire group
+ applySpecAndCheck(node(controller1))
+
+ // THEN the group children are still attached to their parent
+ Assert.assertEquals(controller2.view, controller3.view.parent)
+ Assert.assertEquals(controller2.view, controller4.view.parent)
+ Assert.assertEquals(controller2.view, controller5.view.parent)
+ verifySkipDetachingChildLogged(controller3, parent = controller2)
+ verifySkipDetachingChildLogged(controller4, parent = controller2)
+ verifySkipDetachingChildLogged(controller5, parent = controller2)
+ }
+
+ @Test
+ fun testReuseRemovedGroupsWithKeepInParent() {
+ // GIVEN a preexisting tree with a dismissed group
+ // AND the group children supports keepInParent
+ controller3.supportsKeepInParent = true
+ controller4.supportsKeepInParent = true
+ controller5.supportsKeepInParent = true
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller5))
+ )
+ applySpecAndCheck(node(controller1))
+
+ // WHEN a new spec is applied which reuses the dismissed views
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2),
+ node(controller3),
+ node(controller4),
+ node(controller5)
+ )
+
+ // THEN the dismissed views can be reused
+ Assert.assertEquals(rootController.view, controller3.view.parent)
+ Assert.assertEquals(rootController.view, controller4.view.parent)
+ Assert.assertEquals(rootController.view, controller5.view.parent)
+ verifyDetachingChildLogged(controller3, oldParent = null)
+ verifyDetachingChildLogged(controller4, oldParent = null)
+ verifyDetachingChildLogged(controller5, oldParent = null)
}
@Test
@@ -184,7 +247,30 @@
}
}
+ private fun verifySkipDetachingChildLogged(child: NodeController, parent: NodeController) {
+ verify(logger)
+ .logSkipDetachingChild(
+ key = matches(child.nodeLabel),
+ parentKey = matches(parent.nodeLabel),
+ anyBoolean(),
+ anyBoolean()
+ )
+ }
+
+ private fun verifyDetachingChildLogged(child: NodeController, oldParent: NodeController?) {
+ verify(logger)
+ .logDetachingChild(
+ key = matches(child.nodeLabel),
+ isTransfer = anyBoolean(),
+ isParentRemoved = anyBoolean(),
+ oldParent = oldParent?.let { matches(it.nodeLabel) } ?: isNull(),
+ newParent = isNull()
+ )
+ }
+
private class FakeController(context: Context, label: String) : NodeController {
+ var supportsKeepInParent: Boolean = false
+
override val view: FrameLayout = FrameLayout(context)
override val nodeLabel: String = label
override fun getChildCount(): Int = view.childCount
@@ -209,6 +295,22 @@
override fun onViewAdded() {}
override fun onViewMoved() {}
override fun onViewRemoved() {}
+ override fun offerToKeepInParentForAnimation(): Boolean {
+ return supportsKeepInParent
+ }
+
+ override fun removeFromParentIfKeptForAnimation(): Boolean {
+ if (supportsKeepInParent) {
+ (view.parent as? ViewGroup)?.removeView(view)
+ return true
+ }
+
+ return false
+ }
+
+ override fun resetKeepInParentForAnimation() {
+ supportsKeepInParent = false
+ }
}
private class SpecBuilder(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index b2dc842..7117c23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -41,6 +41,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
@@ -88,6 +89,7 @@
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NotificationListener mListener;
+ @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
private NotificationEntry mEntry;
private TestableNotificationLogger mLogger;
@@ -118,6 +120,7 @@
mVisibilityProvider,
mNotifPipeline,
mock(StatusBarStateControllerImpl.class),
+ mShadeExpansionStateManager,
mBarService,
mExpansionStateLogger
);
@@ -152,7 +155,7 @@
when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+ mLogger.onChildLocationsChanged();
TestableLooper.get(this).processAllMessages();
mUiBgExecutor.runAllReady();
@@ -162,7 +165,7 @@
// |mEntry| won't change visibility, so it shouldn't be reported again:
Mockito.reset(mBarService);
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+ mLogger.onChildLocationsChanged();
TestableLooper.get(this).processAllMessages();
mUiBgExecutor.runAllReady();
@@ -174,7 +177,7 @@
throws Exception {
when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+ mLogger.onChildLocationsChanged();
TestableLooper.get(this).processAllMessages();
mUiBgExecutor.runAllReady();
Mockito.reset(mBarService);
@@ -189,13 +192,13 @@
}
private void setStateAsleep() {
- mLogger.onPanelExpandedChanged(true);
+ mLogger.onShadeExpansionFullyChanged(true);
mLogger.onDozingChanged(true);
mLogger.onStateChanged(StatusBarState.KEYGUARD);
}
private void setStateAwake() {
- mLogger.onPanelExpandedChanged(false);
+ mLogger.onShadeExpansionFullyChanged(false);
mLogger.onDozingChanged(false);
mLogger.onStateChanged(StatusBarState.SHADE);
}
@@ -221,7 +224,7 @@
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
setStateAwake();
// Now expand panel
- mLogger.onPanelExpandedChanged(true);
+ mLogger.onShadeExpansionFullyChanged(true);
assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen);
assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -263,6 +266,7 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateControllerImpl statusBarStateController,
+ ShadeExpansionStateManager shadeExpansionStateManager,
IStatusBarService barService,
ExpansionStateLogger expansionStateLogger) {
super(
@@ -272,6 +276,7 @@
visibilityProvider,
notifPipeline,
statusBarStateController,
+ shadeExpansionStateManager,
expansionStateLogger,
mNotificationPanelLoggerFake
);
@@ -280,9 +285,5 @@
// Make this on the current thread so we can wait for it during tests.
mHandler = Handler.createAsync(Looper.myLooper());
}
-
- OnChildLocationsChangedListener getChildLocationsChangedListenerForTest() {
- return mNotificationLocationsChangedListener;
- }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 12cc114..ee8db18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -458,4 +459,79 @@
verify(mNotificationTestHelper.mOnUserInteractionCallback, never())
.registerFutureDismissal(any(), anyInt());
}
+
+ @Test
+ public void testAddChildNotification() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+ ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+
+ group.addChildNotification(child);
+
+ Assert.assertEquals(child, group.getChildNotificationAt(0));
+ Assert.assertEquals(group, child.getNotificationParent());
+ Assert.assertTrue(child.isChildInGroup());
+ }
+
+ @Test
+ public void testAddChildNotification_childSkipped() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+ ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.addChildNotification(child);
+
+ Assert.assertTrue(group.getAttachedChildren().isEmpty());
+ Assert.assertNotEquals(group, child.getNotificationParent());
+ verify(mNotificationTestHelper.getMockLogger()).logSkipAttachingKeepInParentChild(
+ /*child=*/ child.getEntry(),
+ /*newParent=*/ group.getEntry()
+ );
+ }
+
+ @Test
+ public void testRemoveChildNotification() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.removeChildNotification(child);
+
+ Assert.assertNull(child.getParent());
+ Assert.assertNull(child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger());
+ }
+
+ @Test
+ public void testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.removeChildrenWithKeepInParent();
+
+ Assert.assertNull(child.getParent());
+ Assert.assertNull(child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verify(mNotificationTestHelper.getMockLogger()).logKeepInParentChildDetached(
+ /*child=*/ child.getEntry(),
+ /*oldParent=*/ group.getEntry()
+ );
+ }
+
+ @Test
+ public void testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent()
+ throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+
+ group.removeChildrenWithKeepInParent();
+
+ Assert.assertEquals(group, child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verify(mNotificationTestHelper.getMockLogger(), never()).logKeepInParentChildDetached(
+ /*child=*/ any(),
+ /*oldParent=*/ any()
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 112e759..496bf37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -56,6 +56,7 @@
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -72,7 +73,7 @@
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -115,6 +116,7 @@
private final Context mContext;
private final TestableLooper mTestLooper;
private int mId;
+ private final ExpandableNotificationRowLogger mMockLogger;
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
private ExpandableNotificationRow mRow;
@@ -138,6 +140,7 @@
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
dependency.injectMockDependency(MediaOutputDialogFactory.class);
+ mMockLogger = mock(ExpandableNotificationRowLogger.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mGroupMembershipManager = mock(GroupMembershipManager.class);
mGroupExpansionManager = mock(GroupExpansionManager.class);
@@ -151,7 +154,8 @@
mock(ConfigurationControllerImpl.class),
new Handler(mTestLooper.getLooper()),
mock(AccessibilityManagerWrapper.class),
- mock(UiEventLogger.class)
+ mock(UiEventLogger.class),
+ mock(ShadeExpansionStateManager.class)
);
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
@@ -193,6 +197,10 @@
mDefaultInflationFlags = defaultInflationFlags;
}
+ public ExpandableNotificationRowLogger getMockLogger() {
+ return mMockLogger;
+ }
+
/**
* Creates a generic row with rounded border.
*
@@ -523,7 +531,7 @@
mock(RemoteInputViewSubcomponent.Factory.class),
APP_NAME,
entry.getKey(),
- mock(ExpansionLogger.class),
+ mMockLogger,
mock(KeyguardBypassController.class),
mGroupMembershipManager,
mGroupExpansionManager,
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 90061b0..026c82e 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
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -122,6 +123,7 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
+ @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private StackStateLogger mStackLogger;
@@ -173,6 +175,7 @@
mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
+ mVisibilityLocationProviderDelegator,
mShadeController,
mJankMonitor,
mStackLogger,
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 40aec82..4d9db8c 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
@@ -14,6 +14,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
@@ -31,10 +32,10 @@
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
- private val notificationRow = mock(ExpandableNotificationRow::class.java)
- private val dumpManager = mock(DumpManager::class.java)
- private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java)
- private val notificationShelf = mock(NotificationShelf::class.java)
+ private val notificationRow = mock<ExpandableNotificationRow>()
+ private val dumpManager = mock<DumpManager>()
+ private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+ private val notificationShelf = mock<NotificationShelf>()
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
}
@@ -46,7 +47,7 @@
mStatusBarKeyguardViewManager
)
- private val testableResources = mContext.orCreateTestableResources
+ private val testableResources = mContext.getOrCreateTestableResources()
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -98,7 +99,7 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
val marginBottom =
- context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ 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)
@@ -118,7 +119,7 @@
@Test
fun resetViewStates_expansionChanging_notificationBecomesTransparent() {
- whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction = 0.25f,
expectedAlpha = 0.0f
@@ -127,7 +128,7 @@
@Test
fun resetViewStates_expansionChangingWhileBouncerInTransit_viewBecomesTransparent() {
- whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction = 0.85f,
expectedAlpha = 0.0f
@@ -136,7 +137,7 @@
@Test
fun resetViewStates_expansionChanging_notificationAlphaUpdated() {
- whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction = 0.6f,
expectedAlpha = getContentAlpha(0.6f)
@@ -145,7 +146,7 @@
@Test
fun resetViewStates_expansionChangingWhileBouncerInTransit_notificationAlphaUpdated() {
- whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction = 0.95f,
expectedAlpha = aboutToShowBouncerProgress(0.95f)
@@ -507,6 +508,192 @@
assertEquals(1f, currentRoundness)
}
+ @Test
+ fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
+ // Given: shade is opened, yTranslation of HUN is 0,
+ // the height of HUN equals to the height of QQS Panel,
+ // and HUN fully overlaps with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = false,
+ headerVisibleAmount = 1f
+ )
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: full shadow would be applied
+ assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
+ // Given: shade is opened, yTranslation of HUN is greater than 0,
+ // the height of HUN is equal to the height of QQS Panel,
+ // and HUN partially overlaps with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = false,
+ headerVisibleAmount = 1f
+ )
+ // Use half of the HUN's height as overlap
+ childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have shadow, but not as full size
+ assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+ assertThat(childHunView.viewState.zTranslation)
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ }
+
+ @Test
+ fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() {
+ // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
+ // the height of HUN is equal to the height of QQS Panel,
+ // and HUN doesn't overlap with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = true,
+ headerVisibleAmount = 1f
+ )
+ // HUN doesn't overlap with QQS Panel
+ childHunView.viewState.yTranslation = ambientState.topPadding +
+ ambientState.stackTranslation
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should not have shadow
+ assertEquals(0f, childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun shadeClosed_hunShouldHaveFullShadow() {
+ // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
+ // the height of HUN is equal to the height of QQS Panel,
+ ambientState.stackTranslation = -ambientState.topPadding
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = false,
+ fullyVisible = false,
+ headerVisibleAmount = 0f
+ )
+ childHunView.viewState.yTranslation = 0f
+ // Shade is closed, thus childHunView's headerVisibleAmount is 0
+ childHunView.headerVisibleAmount = 0f
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have full shadow
+ assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
+ // Given: shade is closed when HUN pops up,
+ // now drags down the HUN to open shade
+ ambientState.stackTranslation = -ambientState.topPadding
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = false,
+ fullyVisible = false,
+ headerVisibleAmount = 0.5f
+ )
+ childHunView.viewState.yTranslation = 0f
+ // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
+ // use 0.5 as headerVisibleAmount here
+ childHunView.headerVisibleAmount = 0.5f
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have shadow, but not as full size
+ assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+ assertThat(childHunView.viewState.zTranslation)
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ }
+
+ private fun createHunViewMock(
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ headerVisibleAmount: Float
+ ) =
+ mock<ExpandableNotificationRow>().apply {
+ val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
+ whenever(this.viewState).thenReturn(childViewStateMock)
+
+ whenever(this.mustStayOnScreen()).thenReturn(true)
+ whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+ }
+
+
+ private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+ ExpandableViewState().apply {
+ // Mock the HUN's height with ambientState.topPadding +
+ // ambientState.stackTranslation
+ height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
+ if (isShadeOpen && fullyVisible) {
+ yTranslation =
+ ambientState.topPadding + ambientState.stackTranslation
+ } else {
+ yTranslation = 0f
+ }
+ headsUpIsVisible = fullyVisible
+ }
+
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
expectedAlpha: Float
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index b850cf1..7246116 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -140,24 +140,24 @@
}
@Test
- public void onBiometricAuthenticated_whenFingerprintAndBiometricsDisallowed_showBouncer() {
+ public void onBiometricAuthenticated_fingerprintAndBiometricsDisallowed_showPrimaryBouncer() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */))
.thenReturn(false);
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
}
@Test
- public void onBiometricAuthenticated_whenFingerprint_nonStrongBioDisallowed_showBouncer() {
+ public void onBiometricAuthenticated_fingerprint_nonStrongBioDisallowed_showPrimaryBouncer() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
.thenReturn(false);
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, false /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
assertThat(mBiometricUnlockController.getBiometricType())
@@ -207,7 +207,7 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
@@ -216,7 +216,7 @@
@Test
public void onBiometricAuthenticated_whenFingerprintOnBouncer_dismissBouncer() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
// value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -274,7 +274,7 @@
}
@Test
- public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showBouncer() {
+ public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showPrimaryBouncer() {
reset(mUpdateMonitor);
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
@@ -285,7 +285,7 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FACE, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
}
@@ -314,7 +314,7 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FACE, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_NONE);
}
@@ -322,7 +322,7 @@
@Test
public void onBiometricAuthenticated_whenFaceOnBouncer_dismissBouncer() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
// value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -342,7 +342,7 @@
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
.thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
// value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -369,23 +369,23 @@
}
@Test
- public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+ public void onUdfpsConsecutivelyFailedThreeTimes_showPrimaryBouncer() {
// GIVEN UDFPS is supported
when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
// WHEN udfps fails once - then don't show the bouncer yet
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
// WHEN udfps fails the second time - then don't show the bouncer yet
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
// WHEN udpfs fails the third time
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
// THEN show the bouncer
- verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 5ebaf69..d5bfe1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
@@ -60,6 +61,8 @@
import java.util.Optional;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@@ -84,6 +87,7 @@
@Mock private Vibrator mVibrator;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private SystemBarAttributesListener mSystemBarAttributesListener;
+ @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -115,7 +119,8 @@
Optional.of(mVibrator),
new DisableFlagsLogger(),
DEFAULT_DISPLAY,
- mSystemBarAttributesListener);
+ mSystemBarAttributesListener,
+ mCameraLauncherLazy);
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
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 7ce3a67..013e727 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
@@ -109,9 +109,11 @@
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
@@ -119,7 +121,6 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -220,6 +221,7 @@
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private StatusBarStateControllerImpl mStatusBarStateController;
+ @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
@@ -287,6 +289,8 @@
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
@Mock private WiredChargingRippleController mWiredChargingRippleController;
+ @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
+ @Mock private CameraLauncher mCameraLauncher;
/**
* The process of registering/unregistering a predictive back callback requires a
* ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test.
@@ -339,6 +343,7 @@
mVisibilityProvider,
mock(NotifPipeline.class),
mStatusBarStateController,
+ mShadeExpansionStateManager,
mExpansionStateLogger,
new NotificationPanelLoggerFake()
);
@@ -350,7 +355,6 @@
when(mStackScrollerController.getView()).thenReturn(mStackScroller);
when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
- when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
when(powerManagerService.isInteractive()).thenReturn(true);
when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -378,6 +382,7 @@
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+ when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
@@ -479,7 +484,9 @@
mActivityLaunchAnimator,
mJankMonitor,
mDeviceStateManager,
- mWiredChargingRippleController, mDreamManager) {
+ mWiredChargingRippleController,
+ mDreamManager,
+ mCameraLauncherLazy) {
@Override
protected ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -891,7 +898,7 @@
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
+ when(mCameraLauncher.isLaunchingAffordance()).thenReturn(true);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
}
@@ -927,7 +934,7 @@
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
+ when(mCameraLauncher.isLaunchingAffordance()).thenReturn(false);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
}
@@ -1025,7 +1032,7 @@
@Test
public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
// GIVEN the shade is expanded
- mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
// WHEN collapseShade is called
@@ -1038,7 +1045,7 @@
@Test
public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
// GIVEN the shade is collapsed
- mCentralSurfaces.setPanelExpanded(false);
+ mCentralSurfaces.onShadeExpansionFullyChanged(false);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
// WHEN collapseShade is called
@@ -1051,7 +1058,7 @@
@Test
public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
// GIVEN the shade is expanded & flag enabled
- mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, false);
@@ -1065,7 +1072,7 @@
@Test
public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
// GIVEN the shade is expanded & flag enabled
- mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index fee3ccb..038af8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -14,23 +14,37 @@
package com.android.systemui.statusbar.phone
-import androidx.test.filters.SmallTest
+import android.content.res.Configuration
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.content.res.Configuration.UI_MODE_TYPE_CAR
+import android.os.LocaleList
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import java.util.Locale
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ConfigurationControllerImplTest : SysuiTestCase() {
- private val mConfigurationController =
- com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
+ private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+ @Before
+ fun setUp() {
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+ }
@Test
fun testThemeChange() {
@@ -57,4 +71,303 @@
verify(listener).onThemeChanged()
verify(listener2, never()).onThemeChanged()
}
+
+ @Test
+ fun configChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the config is updated
+ config.densityDpi = 20
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.changedConfig?.densityDpi).isEqualTo(20)
+ assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300)
+ }
+
+ @Test
+ fun densityChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the density is updated
+ config.densityDpi = 20
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun fontChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.fontScale = 1.5f
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the font is updated
+ config.fontScale = 1.4f
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isCarAndUiModeChanged_densityListenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isNotCarAndUiModeChanged_densityListenerNotNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is not notified because it's not car mode
+ assertThat(listener.densityOrFontScaleChanged).isFalse()
+ }
+
+ @Test
+ fun smallestScreenWidthChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the width is updated
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.smallestScreenWidthChanged).isTrue()
+ }
+
+ @Test
+ fun maxBoundsChange_newConfigObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN a new configuration object with new bounds is sent
+ val newConfig = Configuration()
+ newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(newConfig)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+ // Regression test for b/245799099
+ @Test
+ fun maxBoundsChange_sameObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the existing config is updated with new bounds
+ config.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+
+ @Test
+ fun localeListChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the locales are updated
+ config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.localeListChanged).isTrue()
+ }
+
+ @Test
+ fun uiModeChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun layoutDirectionUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the layout is updated
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.layoutDirectionChanged).isTrue()
+ }
+
+ @Test
+ fun assetPathsUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.assetsSeq = 45
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the assets sequence is updated
+ config.assetsSeq = 46
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.themeChanged).isTrue()
+ }
+
+ @Test
+ fun multipleUpdates_listenerNotifiedOfAll() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 14
+ config.windowConfiguration.setMaxBounds(0, 0, 2, 2)
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN multiple fields are updated
+ config.densityDpi = 20
+ config.windowConfiguration.setMaxBounds(0, 0, 3, 3)
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified of all of them
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ assertThat(listener.maxBoundsChanged).isTrue()
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun equivalentConfigObject_listenerNotNotified() {
+ val config = mContext.resources.configuration
+ val listener = createAndAddListener()
+
+ // WHEN we update with the new object that has all the same fields
+ mConfigurationController.onConfigurationChanged(Configuration(config))
+
+ listener.assertNoMethodsCalled()
+ }
+
+ private fun createAndAddListener(): TestListener {
+ val listener = TestListener()
+ mConfigurationController.addCallback(listener)
+ // Adding a listener can trigger some callbacks, so we want to reset the values right
+ // after the listener is added
+ listener.reset()
+ return listener
+ }
+
+ private class TestListener : ConfigurationListener {
+ var changedConfig: Configuration? = null
+ var densityOrFontScaleChanged = false
+ var smallestScreenWidthChanged = false
+ var maxBoundsChanged = false
+ var uiModeChanged = false
+ var themeChanged = false
+ var localeListChanged = false
+ var layoutDirectionChanged = false
+
+ override fun onConfigChanged(newConfig: Configuration?) {
+ changedConfig = newConfig
+ }
+ override fun onDensityOrFontScaleChanged() {
+ densityOrFontScaleChanged = true
+ }
+ override fun onSmallestScreenWidthChanged() {
+ smallestScreenWidthChanged = true
+ }
+ override fun onMaxBoundsChanged() {
+ maxBoundsChanged = true
+ }
+ override fun onUiModeChanged() {
+ uiModeChanged = true
+ }
+ override fun onThemeChanged() {
+ themeChanged = true
+ }
+ override fun onLocaleListChanged() {
+ localeListChanged = true
+ }
+ override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+ layoutDirectionChanged = true
+ }
+
+ fun assertNoMethodsCalled() {
+ assertThat(densityOrFontScaleChanged).isFalse()
+ assertThat(smallestScreenWidthChanged).isFalse()
+ assertThat(maxBoundsChanged).isFalse()
+ assertThat(uiModeChanged).isFalse()
+ assertThat(themeChanged).isFalse()
+ assertThat(localeListChanged).isFalse()
+ assertThat(layoutDirectionChanged).isFalse()
+ }
+
+ fun reset() {
+ changedConfig = null
+ densityOrFontScaleChanged = false
+ smallestScreenWidthChanged = false
+ maxBoundsChanged = false
+ uiModeChanged = false
+ themeChanged = false
+ localeListChanged = false
+ layoutDirectionChanged = false
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index e252401..780e0c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -31,6 +31,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -67,6 +68,7 @@
@Mock private KeyguardBypassController mBypassController;
@Mock private ConfigurationControllerImpl mConfigurationController;
@Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
+ @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock private UiEventLogger mUiEventLogger;
private boolean mLivesPastNormalTime;
@@ -81,7 +83,8 @@
ConfigurationController configurationController,
Handler handler,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ ShadeExpansionStateManager shadeExpansionStateManager
) {
super(
context,
@@ -93,7 +96,8 @@
configurationController,
handler,
accessibilityManagerWrapper,
- uiEventLogger
+ uiEventLogger,
+ shadeExpansionStateManager
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
@@ -125,7 +129,8 @@
mConfigurationController,
mTestHandler,
mAccessibilityManagerWrapper,
- mUiEventLogger
+ mUiEventLogger,
+ mShadeExpansionStateManager
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index ab209d1..d3b5418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -58,7 +58,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Assert;
@@ -86,7 +86,7 @@
@Mock
private KeyguardHostViewController mKeyguardHostViewController;
@Mock
- private BouncerExpansionCallback mExpansionCallback;
+ private KeyguardBouncer.PrimaryBouncerExpansionCallback mExpansionCallback;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
@@ -476,7 +476,8 @@
mBouncer.ensureView();
mBouncer.setExpansion(0.5f);
- final BouncerExpansionCallback callback = mock(BouncerExpansionCallback.class);
+ final PrimaryBouncerExpansionCallback callback =
+ mock(PrimaryBouncerExpansionCallback.class);
mBouncer.addBouncerExpansionCallback(callback);
mBouncer.setExpansion(EXPANSION_HIDDEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 6ec5cf8..eb0b9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -56,14 +56,12 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -112,16 +110,12 @@
private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
@Mock
private UserManager mUserManager;
+ @Mock
+ private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
@Captor
private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
- @Mock
- private StatusBarUserSwitcherFeatureController mStatusBarUserSwitcherFeatureController;
- @Mock
- private StatusBarUserSwitcherController mStatusBarUserSwitcherController;
- @Mock
- private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
@Mock private KeyguardLogger mLogger;
@@ -169,9 +163,7 @@
mStatusBarStateController,
mStatusBarContentInsetsProvider,
mUserManager,
- mStatusBarUserSwitcherFeatureController,
- mStatusBarUserSwitcherController,
- mStatusBarUserInfoTracker,
+ mStatusBarUserChipViewModel,
mSecureSettings,
mCommandQueue,
mFakeExecutor,
@@ -479,8 +471,7 @@
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOn() {
// GIVEN the status bar user switcher chip is enabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(true);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
// WHEN the controller is created
mController = createController();
@@ -492,8 +483,7 @@
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOff() {
// GIVEN the status bar user switcher chip is disabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(false);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
// WHEN the controller is created
mController = createController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index a61fba5..e2843a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,11 +27,11 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.NotificationPanelViewController
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -64,7 +64,7 @@
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
- private lateinit var userSwitcherController: StatusBarUserSwitcherController
+ private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
private lateinit var viewUtil: ViewUtil
@@ -76,17 +76,15 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(notificationPanelViewController.view).thenReturn(panelView)
`when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
.thenReturn(moveFromCenterAnimation)
- // create the view on main thread as it requires main looper
+ // create the view and controller on main thread as it requires main looper
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val parent = FrameLayout(mContext) // add parent to keep layout params
view = LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+ controller = createAndInitController(view)
}
-
- controller = createAndInitController(view)
}
@Test
@@ -106,7 +104,10 @@
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
unfoldConfig.isEnabled = true
- controller = createAndInitController(view)
+ // create the controller on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
argumentCaptor.value.onPreDraw()
@@ -126,7 +127,7 @@
return PhoneStatusBarViewController.Factory(
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
configurationController
).create(view, touchEventHandler).also {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 5aa7f92..27b1da0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -25,7 +25,6 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -41,9 +40,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- // TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when
- // testing just [PhoneStatusBarView].
- `when`(notificationPanelViewController.view).thenReturn(panelView)
view = PhoneStatusBarView(mContext, null)
}
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 696775a..de71e2c 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
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -59,7 +58,6 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -119,7 +117,6 @@
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private KeyguardViewMediator mKeyguardViewMediator;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -233,8 +230,7 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager,
- mKeyguardViewMediator);
+ mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -243,8 +239,6 @@
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
-
- mScrimController.setLaunchingAffordanceWithPreview(false);
}
@After
@@ -858,8 +852,7 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager,
- mKeyguardViewMediator);
+ mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1308,7 +1301,7 @@
@Test
public void qsExpansion_BehindTint_shadeLocked_bouncerActive_usesBouncerProgress() {
- when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
// clipping doesn't change tested logic but allows to assert scrims more in line with
// their expected large screen behaviour
mScrimController.setClipsQsScrim(false);
@@ -1324,7 +1317,7 @@
@Test
public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
- when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.transitionTo(SHADE_LOCKED);
@@ -1340,7 +1333,7 @@
@Test
public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
- when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.transitionTo(SHADE_LOCKED);
@@ -1355,7 +1348,7 @@
@Test
public void notificationAlpha_unnocclusionAnimating_bouncerActive_usesKeyguardNotifAlpha() {
- when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1377,7 +1370,7 @@
@Test
public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
- when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.transitionTo(ScrimState.KEYGUARD);
mScrimController.setUnocclusionAnimationRunning(true);
@@ -1398,7 +1391,7 @@
@Test
public void notificationAlpha_inKeyguardState_bouncerActive_usesInvertedBouncerInterpolator() {
- when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1418,7 +1411,7 @@
@Test
public void notificationAlpha_inKeyguardState_bouncerNotActive_usesInvertedShadeInterpolator() {
- when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1437,6 +1430,17 @@
}
@Test
+ public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
+ mScrimController.setClipsQsScrim(false);
+
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ finishAnimationsImmediately();
+ assertThat(mScrimBehind.getTint())
+ .isEqualTo(ScrimState.KEYGUARD.getBehindTint());
+ }
+
+ @Test
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
@@ -1627,30 +1631,6 @@
assertScrimAlpha(mScrimBehind, 0);
}
- @Test
- public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
-
- when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
-
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
-
- assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
- }
-
- @Test
- public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
- when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
- mScrimController.setLaunchingAffordanceWithPreview(true);
-
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
-
- assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
- }
-
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index e86676b..1759fb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,9 +19,9 @@
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -463,16 +463,10 @@
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
// WHEN: get insets on the second display
val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -487,23 +481,15 @@
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsetsFirstCall = provider
.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
// WHEN: get insets on the first display again
val firstDisplayInsetsSecondCall = provider
@@ -513,9 +499,70 @@
assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
}
- private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
- `when`(display.uniqueId).thenReturn(displayUniqueId)
- configuration.windowConfiguration.maxBounds = screenBounds
+ // Regression test for b/245799099
+ @Test
+ fun onMaxBoundsChanged_listenerNotified() {
+ // Start out with an existing configuration with bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ configurationController.onConfigurationChanged(configuration)
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated with new bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onDensityOrFontScaleChanged_listenerNotified() {
+ configuration.densityDpi = 12
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated
+ configuration.densityDpi = 20
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onThemeChanged_listenerNotified() {
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ configurationController.notifyThemeChanged()
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
}
private fun assertRects(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ec8d711..bf5186b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -56,8 +56,8 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.BouncerView;
import com.android.systemui.keyguard.data.BouncerViewDelegate;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -105,8 +105,8 @@
@Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
@Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
- @Mock private KeyguardBouncer mBouncer;
- @Mock private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
+ @Mock private KeyguardBouncer mPrimaryBouncer;
+ @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
@Mock private KeyguardMessageArea mKeyguardMessageArea;
@Mock private ShadeController mShadeController;
@Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@@ -114,13 +114,13 @@
@Mock private LatencyTracker mLatencyTracker;
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
- @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
- @Mock private BouncerInteractor mBouncerInteractor;
+ @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+ @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock private BouncerView mBouncerView;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+ private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
private FakeKeyguardStateController mKeyguardStateController =
spy(new FakeKeyguardStateController());
@@ -134,8 +134,9 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mKeyguardBouncerFactory.create(
- any(ViewGroup.class), any(KeyguardBouncer.BouncerExpansionCallback.class)))
- .thenReturn(mBouncer);
+ any(ViewGroup.class),
+ any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
+ .thenReturn(mPrimaryBouncer);
when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
@@ -163,8 +164,8 @@
mLatencyTracker,
mKeyguardSecurityModel,
mFeatureFlags,
- mBouncerCallbackInteractor,
- mBouncerInteractor,
+ mPrimaryBouncerCallbackInteractor,
+ mPrimaryBouncerInteractor,
mBouncerView) {
@Override
public ViewRootImpl getViewRootImpl() {
@@ -181,8 +182,8 @@
mNotificationContainer,
mBypassController);
mStatusBarKeyguardViewManager.show(null);
- ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> callbackArgumentCaptor =
- ArgumentCaptor.forClass(KeyguardBouncer.BouncerExpansionCallback.class);
+ ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
callbackArgumentCaptor.capture());
mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
@@ -194,86 +195,86 @@
Runnable cancelAction = () -> {};
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, false /* afterKeyguardGone */);
- verify(mBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+ verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
}
@Test
public void showBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
- verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mBouncer, never()).show(anyBoolean());
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncer, never()).show(anyBoolean());
}
@Test
public void showBouncer_notWhenBouncerAlreadyShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- when(mBouncer.isSecure()).thenReturn(true);
- mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
- verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
- verify(mBouncer, never()).show(anyBoolean());
+ when(mPrimaryBouncer.isSecure()).thenReturn(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mPrimaryBouncer, never()).show(anyBoolean());
}
@Test
public void showBouncer_showsTheBouncer() {
- mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
- verify(mBouncer).show(anyBoolean(), eq(true));
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
}
@Test
- public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
- when(mBouncer.isShowing()).thenReturn(true);
- when(mBouncer.isScrimmed()).thenReturn(true);
+ public void onPanelExpansionChanged_neverHidesFullscreenBouncer() {
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPuk);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
+
+ reset(mPrimaryBouncer);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
}
@Test
public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
}
@Test
public void onPanelExpansionChanged_propagatesToBouncer() {
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer).setExpansion(eq(0.5f));
+ verify(mPrimaryBouncer).setExpansion(eq(0.5f));
}
@Test
public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
mStatusBarKeyguardViewManager.hide(0, 0);
- when(mBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
}
@Test
public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
mKeyguardStateController.setCanDismissLockScreen(false);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer).show(eq(false), eq(false));
+ verify(mPrimaryBouncer).show(eq(false), eq(false));
// But not when it's already visible
- reset(mBouncer);
- when(mBouncer.isShowing()).thenReturn(true);
+ reset(mPrimaryBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer, never()).show(eq(false), eq(false));
+ verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
// Or animating away
- reset(mBouncer);
- when(mBouncer.isAnimatingAway()).thenReturn(true);
+ reset(mPrimaryBouncer);
+ when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer, never()).show(eq(false), eq(false));
- }
-
- @Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animate */);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer, never()).setExpansion(eq(0.5f));
+ verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
}
@Test
@@ -285,7 +286,7 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
}
@Test
@@ -302,7 +303,24 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+ // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+ // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+ // which would mistakenly cause the bouncer to show briefly before its visibility
+ // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+ // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
}
@Test
@@ -313,7 +331,7 @@
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
/* expanded= */ true,
/* tracking= */ false));
- verify(mBouncer, never()).setExpansion(anyFloat());
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
}
@Test
@@ -321,7 +339,7 @@
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
verify(mCentralSurfaces).animateKeyguardUnoccluding();
- when(mBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
clearInvocations(mCentralSurfaces);
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
@@ -372,7 +390,7 @@
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, true /* afterKeyguardGone */);
- when(mBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
mStatusBarKeyguardViewManager.hideBouncer(true);
mStatusBarKeyguardViewManager.hide(0, 30);
verify(action, never()).onDismiss();
@@ -386,7 +404,7 @@
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, true /* afterKeyguardGone */);
- when(mBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
mStatusBarKeyguardViewManager.hideBouncer(true);
verify(action, never()).onDismiss();
@@ -407,9 +425,9 @@
@Test
public void testShowing_whenAlternateAuthShowing() {
- mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- when(mBouncer.isShowing()).thenReturn(false);
- when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
assertTrue(
"Is showing not accurate when alternative auth showing",
mStatusBarKeyguardViewManager.isBouncerShowing());
@@ -417,93 +435,93 @@
@Test
public void testWillBeShowing_whenAlternateAuthShowing() {
- mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- when(mBouncer.isShowing()).thenReturn(false);
- when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
assertTrue(
"Is or will be showing not accurate when alternative auth showing",
- mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
+ mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
}
@Test
- public void testHideAltAuth_onShowBouncer() {
+ public void testHideAlternateBouncer_onShowBouncer() {
// GIVEN alt auth is showing
- mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- when(mBouncer.isShowing()).thenReturn(false);
- when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
- reset(mAlternateAuthInterceptor);
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+ reset(mAlternateBouncer);
// WHEN showBouncer is called
- mStatusBarKeyguardViewManager.showBouncer(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
// THEN alt bouncer should be hidden
- verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer();
+ verify(mAlternateBouncer).hideAlternateBouncer();
}
@Test
public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
- when(mBouncer.isShowing()).thenReturn(false);
- when(mBouncer.inTransit()).thenReturn(true);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
assertTrue(
"Is or will be showing should be true when bouncer is in transit",
- mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
+ mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
}
@Test
public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
// GIVEN alt auth exists, unlocking with biometric isn't allowed
- mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- when(mBouncer.isShowing()).thenReturn(false);
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
.thenReturn(false);
// WHEN showGenericBouncer is called
final boolean scrimmed = true;
- mStatusBarKeyguardViewManager.showGenericBouncer(scrimmed);
+ mStatusBarKeyguardViewManager.showBouncer(scrimmed);
// THEN regular bouncer is shown
- verify(mBouncer).show(anyBoolean(), eq(scrimmed));
- verify(mAlternateAuthInterceptor, never()).showAlternateAuthBouncer();
+ verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+ verify(mAlternateBouncer, never()).showAlternateBouncer();
}
@Test
- public void testShowAltAuth_unlockingWithBiometricAllowed() {
+ public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
// GIVEN alt auth exists, unlocking with biometric is allowed
- mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- when(mBouncer.isShowing()).thenReturn(false);
+ mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+ when(mPrimaryBouncer.isShowing()).thenReturn(false);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
// WHEN showGenericBouncer is called
- mStatusBarKeyguardViewManager.showGenericBouncer(true);
+ mStatusBarKeyguardViewManager.showBouncer(true);
// THEN alt auth bouncer is shown
- verify(mAlternateAuthInterceptor).showAlternateAuthBouncer();
- verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
+ verify(mAlternateBouncer).showAlternateBouncer();
+ verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
}
@Test
public void testUpdateResources_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateResources();
- verify(mBouncer).updateResources();
+ verify(mPrimaryBouncer).updateResources();
}
@Test
public void updateKeyguardPosition_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
- verify(mBouncer).updateKeyguardPosition(1.0f);
+ verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
}
@Test
public void testIsBouncerInTransit() {
- when(mBouncer.inTransit()).thenReturn(true);
- Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isTrue();
- when(mBouncer.inTransit()).thenReturn(false);
- Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
- mBouncer = null;
- Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
+ when(mPrimaryBouncer.inTransit()).thenReturn(true);
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
+ when(mPrimaryBouncer.inTransit()).thenReturn(false);
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+ mPrimaryBouncer = null;
+ Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
}
private static ShadeExpansionChangeEvent expansionEvent(
@@ -534,7 +552,7 @@
eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
mOnBackInvokedCallback.capture());
- when(mBouncer.isShowing()).thenReturn(true);
+ when(mPrimaryBouncer.isShowing()).thenReturn(true);
when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
/* invoke the back callback directly */
mOnBackInvokedCallback.getValue().onBackInvoked();
@@ -567,6 +585,42 @@
public void flag_off_DoesNotCallBouncerInteractor() {
when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
mStatusBarKeyguardViewManager.hideBouncer(false);
- verify(mBouncerInteractor, never()).hide();
+ verify(mPrimaryBouncerInteractor, never()).hide();
+ }
+
+ @Test
+ public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+ mStatusBarKeyguardViewManager =
+ new StatusBarKeyguardViewManager(
+ getContext(),
+ mViewMediatorCallback,
+ mLockPatternUtils,
+ mStatusBarStateController,
+ mock(ConfigurationController.class),
+ mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
+ mock(NavigationModeController.class),
+ mock(DockManager.class),
+ mock(NotificationShadeWindowController.class),
+ mKeyguardStateController,
+ mock(NotificationMediaManager.class),
+ mKeyguardBouncerFactory,
+ mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
+ () -> mShadeController,
+ mLatencyTracker,
+ mKeyguardSecurityModel,
+ mFeatureFlags,
+ mPrimaryBouncerCallbackInteractor,
+ mPrimaryBouncerInteractor,
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+
+ // the following call before registering centralSurfaces should NOT throw a NPE:
+ mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index c3a7e65..613238f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -99,6 +99,6 @@
mRemoteInputCallback.onLockedRemoteInput(
mock(ExpandableNotificationRow.class), mock(View.class));
- verify(mStatusBarKeyguardViewManager).showGenericBouncer(true);
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index eba3b04..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,102 +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.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-@SmallTest
-class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock
- private lateinit var tracker: StatusBarUserInfoTracker
-
- @Mock
- private lateinit var featureController: StatusBarUserSwitcherFeatureController
-
- @Mock
- private lateinit var userSwitcherDialogController: UserSwitchDialogController
-
- @Mock
- private lateinit var featureFlags: FeatureFlags
-
- @Mock
- private lateinit var activityStarter: ActivityStarter
-
- @Mock
- private lateinit var falsingManager: FalsingManager
-
- private lateinit var statusBarUserSwitcherContainer: StatusBarUserSwitcherContainer
- private lateinit var controller: StatusBarUserSwitcherControllerImpl
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- statusBarUserSwitcherContainer = StatusBarUserSwitcherContainer(mContext, null)
- statusBarUserSwitcherContainer
- controller = StatusBarUserSwitcherControllerImpl(
- statusBarUserSwitcherContainer,
- tracker,
- featureController,
- userSwitcherDialogController,
- featureFlags,
- activityStarter,
- falsingManager
- )
- controller.init()
- controller.onViewAttached()
- }
-
- @Test
- fun testFalsingManager() {
- statusBarUserSwitcherContainer.callOnClick()
- verify(falsingManager).isFalseTap(FalsingManager.LOW_PENALTY)
- }
-
- @Test
- fun testStartActivity() {
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
- statusBarUserSwitcherContainer.callOnClick()
- verify(userSwitcherDialogController).showDialog(any(), any())
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
- statusBarUserSwitcherContainer.callOnClick()
- verify(activityStarter).startActivity(any(Intent::class.java),
- eq(true) /* dismissShade */,
- eq(null) /* animationController */,
- eq(true) /* showOverLockscreenWhenLocked */,
- eq(UserHandle.SYSTEM))
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
index 6d8d902..a052008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
@@ -16,31 +16,59 @@
package com.android.systemui.statusbar.pipeline.mobile.util
+import android.telephony.TelephonyDisplayInfo
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.settingslib.mobile.TelephonyIcons
class FakeMobileMappingsProxy : MobileMappingsProxy {
+ // The old [NetworkControllerDataTest] infra requires us to be able to use the real
+ // impl sometimes
+ var useRealImpl = false
+
+ private var realImpl = MobileMappingsProxyImpl()
private var iconMap = mapOf<String, MobileIconGroup>()
private var defaultIcons = TelephonyIcons.THREE_G
fun setIconMap(map: Map<String, MobileIconGroup>) {
iconMap = map
}
- override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> {
+ if (useRealImpl) {
+ return realImpl.mapIconSets(config)
+ }
+ return iconMap
+ }
fun getIconMap() = iconMap
fun setDefaultIcons(group: MobileIconGroup) {
defaultIcons = group
}
- override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons
+ override fun getDefaultIcons(config: Config): MobileIconGroup {
+ if (useRealImpl) {
+ return realImpl.getDefaultIcons(config)
+ }
+ return defaultIcons
+ }
+
+ /** This is only used in the old pipeline, use the real impl always */
+ override fun getIconKey(displayInfo: TelephonyDisplayInfo): String {
+ return realImpl.getIconKey(displayInfo)
+ }
+
fun getDefaultIcons(): MobileIconGroup = defaultIcons
override fun toIconKey(networkType: Int): String {
+ if (useRealImpl) {
+ return realImpl.toIconKeyOverride(networkType)
+ }
return networkType.toString()
}
override fun toIconKeyOverride(networkType: Int): String {
+ if (useRealImpl) {
+ return realImpl.toIconKeyOverride(networkType)
+ }
return toIconKey(networkType) + "_override"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
index f304647..0a3da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -237,7 +237,7 @@
fun refresh() {
underTest.refresh()
- verify(controller).refreshUsers(UserHandle.USER_NULL)
+ verify(controller).refreshUsers()
}
private fun createUserRecord(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 43d0fe9..1eee08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -221,4 +221,33 @@
Assert.assertFalse(mBatteryController.isChargingSourceDock());
}
+
+ @Test
+ public void batteryStateChanged_healthNotOverheated_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_healthOverheated_outputsTrue() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_noHealthGiven_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index d0391ac..833cabb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,7 @@
@SmallTest
public class BluetoothControllerImplTest extends SysuiTestCase {
+ private UserTracker mUserTracker;
private LocalBluetoothManager mMockBluetoothManager;
private CachedBluetoothDeviceManager mMockDeviceManager;
private LocalBluetoothAdapter mMockAdapter;
@@ -70,6 +72,7 @@
mTestableLooper = TestableLooper.get(this);
mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
mDevices = new ArrayList<>();
+ mUserTracker = mock(UserTracker.class);
mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
@@ -81,6 +84,7 @@
mMockDumpManager = mock(DumpManager.class);
mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+ mUserTracker,
mMockDumpManager,
mock(BluetoothLogger.class),
mTestableLooper.getLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
index 14cc032..71ac7c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -33,7 +33,7 @@
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 26df03f..dc08aba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +62,8 @@
public class HotspotControllerImplTest extends SysuiTestCase {
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private DumpManager mDumpManager;
@Mock
private TetheringManager mTetheringManager;
@@ -104,7 +107,8 @@
Handler handler = new Handler(mLooper.getLooper());
- mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+ mController = new HotspotControllerImpl(mContext, mUserTracker, handler, handler,
+ mDumpManager);
verify(mTetheringManager)
.registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture());
}
@@ -191,7 +195,7 @@
Handler handler = new Handler(mLooper.getLooper());
HotspotController controller =
- new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+ new HotspotControllerImpl(mContext, mUserTracker, handler, handler, mDumpManager);
verifyNoMoreInteractions(mTetheringManager);
assertFalse(controller.isHotspotSupported());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index d44cdb2..15235b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,10 +73,12 @@
private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
private final UserManager mUserManager = mock(UserManager.class);
+ private final UserTracker mUserTracker = mock(UserTracker.class);
private final BroadcastDispatcher mBroadcastDispatcher = mock(BroadcastDispatcher.class);
private final Handler mHandler = mock(Handler.class);
private SecurityControllerImpl mSecurityController;
private ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
+ private FakeExecutor mMainExecutor;
private FakeExecutor mBgExecutor;
private BroadcastReceiver mBroadcastReceiver;
@@ -102,11 +105,14 @@
ArgumentCaptor<BroadcastReceiver> brCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
+ mMainExecutor = new FakeExecutor(new FakeSystemClock());
mBgExecutor = new FakeExecutor(new FakeSystemClock());
mSecurityController = new SecurityControllerImpl(
mContext,
+ mUserTracker,
mHandler,
mBroadcastDispatcher,
+ mMainExecutor,
mBgExecutor,
Mockito.mock(DumpManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index 169f4fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.policy
-
-import android.app.IActivityManager
-import android.app.NotificationManager
-import android.app.admin.DevicePolicyManager
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.hardware.face.FaceManager
-import android.hardware.fingerprint.FingerprintManager
-import android.os.Handler
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.ThreadedRenderer
-import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.LatencyTracker
-import com.android.internal.util.UserIcons
-import com.android.systemui.GuestResetOrExitSessionReceiver
-import com.android.systemui.GuestResumeSessionReceiver
-import com.android.systemui.GuestSessionNotification
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.NotificationShadeWindowView
-import com.android.systemui.telephony.TelephonyListenerManager
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.concurrency.FakeExecutor
-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.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-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.doNothing
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class UserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var activityManager: IActivityManager
- @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var handler: Handler
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var userManager: UserManager
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var broadcastSender: BroadcastSender
- @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
- @Mock private lateinit var secureSettings: SecureSettings
- @Mock private lateinit var falsingManager: FalsingManager
- @Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock private lateinit var latencyTracker: LatencyTracker
- @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
- @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
- @Mock private lateinit var threadedRenderer: ThreadedRenderer
- @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
- @Mock private lateinit var globalSettings: GlobalSettings
- @Mock private lateinit var guestSessionNotification: GuestSessionNotification
- @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
- private lateinit var resetSessionDialogFactory:
- GuestResumeSessionReceiver.ResetSessionDialog.Factory
- private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
- private lateinit var testableLooper: TestableLooper
- private lateinit var bgExecutor: FakeExecutor
- private lateinit var longRunningExecutor: FakeExecutor
- private lateinit var uiExecutor: FakeExecutor
- private lateinit var uiEventLogger: UiEventLoggerFake
- private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
- private lateinit var picture: Bitmap
- private val ownerId = UserHandle.USER_SYSTEM
- private val ownerInfo = UserInfo(ownerId, "Owner", null,
- UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
- UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
- UserManager.USER_TYPE_FULL_SYSTEM)
- private val guestId = 1234
- private val guestInfo = UserInfo(guestId, "Guest", null,
- UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
- private val secondaryUser =
- UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- testableLooper = TestableLooper.get(this)
- bgExecutor = FakeExecutor(FakeSystemClock())
- longRunningExecutor = FakeExecutor(FakeSystemClock())
- uiExecutor = FakeExecutor(FakeSystemClock())
- uiEventLogger = UiEventLoggerFake()
-
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_guestUserAutoCreated, false)
-
- mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
- mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
- mock(NotificationManager::class.java))
- mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
- mock(FingerprintManager::class.java))
-
- resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
- override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
- return GuestResumeSessionReceiver.ResetSessionDialog(
- mContext,
- mock(UserSwitcherController::class.java),
- uiEventLogger,
- userId
- )
- }
- }
-
- guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
- secureSettings,
- broadcastDispatcher,
- guestSessionNotification,
- resetSessionDialogFactory)
-
- `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
- .thenReturn(true)
- `when`(notificationShadeWindowView.context).thenReturn(context)
-
- // Since userSwitcherController involves InteractionJankMonitor.
- // Let's fulfill the dependencies.
- val mockedContext = mock(Context::class.java)
- doReturn(mockedContext).`when`(notificationShadeWindowView).context
- doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
- doNothing().`when`(threadedRenderer).addObserver(any())
- doNothing().`when`(threadedRenderer).removeObserver(any())
- doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
-
- picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
-
- // Create defaults for the current user
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- setupController()
- }
-
- private fun setupController() {
- userSwitcherController =
- UserSwitcherControllerOldImpl(
- mContext,
- activityManager,
- userManager,
- userTracker,
- keyguardStateController,
- deviceProvisionedController,
- devicePolicyManager,
- handler,
- activityStarter,
- broadcastDispatcher,
- broadcastSender,
- uiEventLogger,
- falsingManager,
- telephonyListenerManager,
- secureSettings,
- globalSettings,
- bgExecutor,
- longRunningExecutor,
- uiExecutor,
- interactionJankMonitor,
- latencyTracker,
- dumpManager,
- dialogLaunchAnimator,
- guestResumeSessionReceiver,
- guestResetOrExitSessionReceiver
- )
- userSwitcherController.init(notificationShadeWindowView)
- }
-
- @Test
- fun testSwitchUser_parentDialogDismissed() {
- val otherUserRecord = UserRecord(
- secondaryUser,
- picture,
- false /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
- testableLooper.processAllMessages()
-
- verify(dialogShower).dismiss()
- }
-
- @Test
- fun testAddGuest_okButtonPressed() {
- val emptyGuestUserRecord =
- UserRecord(
- null,
- null,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
- bgExecutor.runAllReady()
- uiExecutor.runAllReady()
- testableLooper.processAllMessages()
- verify(interactionJankMonitor).begin(any())
- verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
- verify(activityManager).switchUser(guestInfo.id)
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun testAddGuest_parentDialogDismissed() {
- val emptyGuestUserRecord =
- UserRecord(
- null,
- null,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
- bgExecutor.runAllReady()
- uiExecutor.runAllReady()
- testableLooper.processAllMessages()
- verify(dialogShower).dismiss()
- }
-
- @Test
- fun testRemoveGuest_removeButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertTrue(
- QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
- QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
- )
- }
-
- @Test
- fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
- testableLooper.processAllMessages()
- assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
- }
-
- @Test
- fun testRemoveGuest_dialogShowerUsed() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- testableLooper.processAllMessages()
- verify(dialogShower)
- .showDialog(
- userSwitcherController.mExitGuestDialog,
- DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode"))
- }
-
- @Test
- fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
- testableLooper.processAllMessages()
- assertEquals(0, uiEventLogger.numLogs())
- }
-
- @Test
- fun testWipeGuest_startOverButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- // Simulate that guest user has already logged in
- `when`(secureSettings.getIntForUser(
- eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
- .thenReturn(1)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
- // Simulate a user switch event
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
- userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
- userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
- .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun testWipeGuest_continueButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- // Simulate that guest user has already logged in
- `when`(secureSettings.getIntForUser(
- eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
- .thenReturn(1)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
- // Simulate a user switch event
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
- userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
- userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
- .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE)
- .performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() {
- fun addUser(id: Int, name: String, isCurrent: Boolean) {
- userSwitcherController.users.add(
- UserRecord(
- UserInfo(id, name, 0),
- null, false, isCurrent, false,
- false, false, false
- )
- )
- }
- val bgUserName = "background_user"
- val fgUserName = "foreground_user"
-
- addUser(1, bgUserName, false)
- addUser(2, fgUserName, true)
-
- assertEquals(fgUserName, userSwitcherController.currentUserName)
- }
-
- @Test
- fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
- `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
- assertEquals(true, userSwitcherController.isSystemUser)
- }
-
- @Test
- fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
- `when`(userTracker.userId).thenReturn(1)
- assertEquals(false, userSwitcherController.isSystemUser)
- }
-
- @Test
- fun testCanCreateSupervisedUserWithConfiguredPackage() {
- // GIVEN the supervised user creation package is configured
- `when`(context.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage))
- .thenReturn("some_pkg")
-
- // AND the current user is allowed to create new users
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- // WHEN the controller is started with the above config
- setupController()
- testableLooper.processAllMessages()
-
- // THEN a supervised user can be constructed
- assertTrue(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCannotCreateSupervisedUserWithConfiguredPackage() {
- // GIVEN the supervised user creation package is NOT configured
- `when`(context.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage))
- .thenReturn(null)
-
- // AND the current user is allowed to create new users
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- // WHEN the controller is started with the above config
- setupController()
- testableLooper.processAllMessages()
-
- // THEN a supervised user can NOT be constructed
- assertFalse(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCannotCreateUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateUser())
- }
-
- @Test
- fun testCannotCreateGuestUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateGuest(false))
- }
-
- @Test
- fun testCannotCreateSupervisedUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
- setupController()
- assertTrue(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
- setupController()
- assertFalse(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherEnabled_isAdmin() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- setupController()
- assertTrue(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherDisabled_isAdmin() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- setupController()
- assertFalse(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun addUserSwitchCallback() {
- val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
- verify(broadcastDispatcher).registerReceiver(
- capture(broadcastReceiverCaptor),
- any(),
- nullable(), nullable(), anyInt(), nullable())
-
- val cb = mock(UserSwitcherController.UserSwitchCallback::class.java)
- userSwitcherController.addUserSwitchCallback(cb)
-
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
- broadcastReceiverCaptor.value.onReceive(context, intent)
- verify(cb).onUserSwitched()
- }
-
- @Test
- fun onUserItemClicked_guest_runsOnBgThread() {
- val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
- val guestUserRecord = UserRecord(
- null,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
-
- userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
- assertTrue(bgExecutor.numPending() > 0)
- verify(userManager, never()).createGuest(context)
- bgExecutor.runAllReady()
- verify(userManager).createGuest(context)
- }
-
- @Test
- fun onUserItemClicked_manageUsers() {
- val manageUserRecord = LegacyUserDataHelper.createRecord(
- mContext,
- ownerId,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- isRestricted = false,
- isSwitchToEnabled = true
- )
-
- userSwitcherController.onUserListItemClicked(manageUserRecord, null)
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(),
- eq(true)
- )
- Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 3fe1a9f..c06dbdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.ZenModeController.Callback;
import com.android.systemui.util.settings.FakeSettings;
@@ -58,6 +59,8 @@
BroadcastDispatcher mBroadcastDispatcher;
@Mock
DumpManager mDumpManager;
+ @Mock
+ UserTracker mUserTracker;
private ZenModeControllerImpl mController;
@@ -72,7 +75,8 @@
Handler.createAsync(Looper.myLooper()),
mBroadcastDispatcher,
mDumpManager,
- new FakeSettings());
+ new FakeSettings(),
+ mUserTracker);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
index 05512e5..0d19ab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
new file mode 100644
index 0000000..2024d53
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.surfaceeffects.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiRippleViewTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun onRippleFinishes_triggersRippleFinished() {
+ val multiRippleView = MultiRippleView(context, null)
+ val multiRippleController = MultiRippleController(multiRippleView)
+ val rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
+
+ var isTriggered = false
+ val listener =
+ object : MultiRippleView.Companion.RipplesFinishedListener {
+ override fun onRipplesFinish() {
+ isTriggered = true
+ }
+ }
+ multiRippleView.addRipplesFinishedListener(listener)
+
+ fakeExecutor.execute {
+ val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+ multiRippleController.play(rippleAnimation)
+
+ fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
+
+ assertThat(isTriggered).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
index 7662282..756397a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
index 2d2f4cc..1e5ab7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -21,12 +21,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
@SmallTest
@RunWith(AndroidTestingRunner::class)
class RippleViewTest : SysuiTestCase() {
- @Mock
private lateinit var rippleView: RippleView
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
new file mode 100644
index 0000000..d25c8c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseControllerTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun play_playsTurbulenceNoise() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(turbulenceNoiseView.isPlaying).isFalse()
+ }
+ }
+
+ @Test
+ fun updateColor_updatesCorrectColor() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f, color = Color.WHITE)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ val expectedColor = Color.RED
+
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(config)
+
+ turbulenceNoiseView.updateColor(expectedColor)
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(config.color).isEqualTo(expectedColor)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
new file mode 100644
index 0000000..633aac0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseViewTest : SysuiTestCase() {
+
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun play_viewHasCorrectVisibility() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE)
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+ }
+ }
+
+ @Test
+ fun play_playsAnimation() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+ }
+ }
+
+ @Test
+ fun play_onEnd_triggersOnAnimationEnd() {
+ var animationEnd = false
+ val config =
+ TurbulenceNoiseAnimationConfig(
+ duration = 1000f,
+ onAnimationEnd = { animationEnd = true }
+ )
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(animationEnd).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 9dea48e..09f0d4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -119,31 +119,41 @@
)
)
- verify(logger).logViewAddition("Fake Window Title")
+ verify(logger).logViewAddition("id", "Fake Window Title")
}
@Test
- fun displayView_screenOff_wakeLockAcquired() {
+ fun displayView_wakeLockAcquired() {
underTest.displayView(getState())
assertThat(fakeWakeLock.isHeld).isTrue()
}
@Test
- fun displayView_screenAlreadyOn_wakeLockNotAcquired() {
+ fun displayView_screenAlreadyOn_wakeLockAcquired() {
whenever(powerManager.isScreenOn).thenReturn(true)
underTest.displayView(getState())
+ assertThat(fakeWakeLock.isHeld).isTrue()
+ }
+
+ @Test
+ fun displayView_wakeLockCanBeReleasedAfterTimeOut() {
+ underTest.displayView(getState())
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
assertThat(fakeWakeLock.isHeld).isFalse()
}
@Test
- fun displayView_screenOff_wakeLockCanBeReleasedAfterTimeOut() {
+ fun displayView_removeView_wakeLockCanBeReleased() {
underTest.displayView(getState())
assertThat(fakeWakeLock.isHeld).isTrue()
- fakeClock.advanceTime(TIMEOUT_MS + 1)
+ underTest.removeView("id", "test reason")
assertThat(fakeWakeLock.isHeld).isFalse()
}
@@ -253,21 +263,143 @@
}
@Test
+ fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.removeView("id2", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.removeView("id1", "test reason")
+
+ verify(windowManager, never()).removeView(any())
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.displayView(ViewInfo("Third name", id = "id3"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.removeView("id3", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+
+ reset(windowManager)
+ underTest.removeView("id2", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_oneViewStateChanged_stackHasRecentState() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+ underTest.displayView(ViewInfo("New name", id = "id1"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.removeView("id2", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
+ assertThat(underTest.activeViews[0].second.name).isEqualTo("New name")
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+ fakeClock.advanceTime(TIMEOUT_MS / 3)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ fakeClock.advanceTime(TIMEOUT_MS / 3)
+ underTest.displayView(ViewInfo("Third name", id = "id3"))
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
fun removeView_viewRemovedAndRemovalLogged() {
// First, add the view
underTest.displayView(getState())
// Then, remove it
val reason = "test reason"
- underTest.removeView(reason)
+ val deviceId = "id"
+ underTest.removeView(deviceId, reason)
verify(windowManager).removeView(any())
- verify(logger).logViewRemoval(reason)
+ verify(logger).logViewRemoval(deviceId, reason)
}
@Test
fun removeView_noAdd_viewNotRemoved() {
- underTest.removeView("reason")
+ underTest.removeView("id", "reason")
verify(windowManager, never()).removeView(any())
}
@@ -319,7 +451,8 @@
val name: String,
override val windowTitle: String = "Window Title",
override val wakeReason: String = "WAKE_REASON",
- override val timeoutMs: Int = 1
+ override val timeoutMs: Int = 1,
+ override val id: String = "id",
) : TemporaryViewInfo()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index d155050..116b8fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -44,7 +44,7 @@
@Test
fun logViewAddition_bufferHasLog() {
- logger.logViewAddition("Test Window Title")
+ logger.logViewAddition("test id", "Test Window Title")
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -57,7 +57,8 @@
@Test
fun logViewRemoval_bufferHasTagAndReason() {
val reason = "test reason"
- logger.logViewRemoval(reason)
+ val deviceId = "test id"
+ logger.logViewRemoval(deviceId, reason)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -65,6 +66,7 @@
assertThat(actualString).contains(TAG)
assertThat(actualString).contains(reason)
+ assertThat(actualString).contains(deviceId)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 8e37aa2..47c84ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -377,6 +377,7 @@
windowTitle = WINDOW_TITLE,
wakeReason = WAKE_REASON,
timeoutMs = TIMEOUT,
+ id = DEVICE_ID,
)
}
@@ -401,3 +402,4 @@
private const val TIMEOUT = 10000
private const val WINDOW_TITLE = "Test Chipbar Window Title"
private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"
+private const val DEVICE_ID = "id"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 797f86a..27957ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -62,7 +62,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 8645298..89402de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -88,6 +88,7 @@
deviceStates = FoldableTestUtils.findDeviceStates(context)
+ // TODO(b/254878364): remove this call to NPVC.getView()
whenever(notificationPanelViewController.view).thenReturn(viewGroup)
whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
whenever(wakefulnessLifecycle.lastSleepReason)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index fc2a78a..e1e54a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -15,14 +15,13 @@
*/
package com.android.systemui.unfold.util
-import android.animation.ValueAnimator
import android.content.ContentResolver
import android.database.ContentObserver
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.util.mockito.any
import org.junit.Before
@@ -30,6 +29,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@@ -38,30 +38,25 @@
@SmallTest
class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
- @Mock
- lateinit var contentResolver: ContentResolver
-
- @Mock
- lateinit var sinkProvider: TransitionProgressListener
+ @Mock lateinit var sinkProvider: TransitionProgressListener
private val sourceProvider = TestUnfoldTransitionProvider()
- lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+ private lateinit var contentResolver: ContentResolver
+ private lateinit var progressProvider: ScaleAwareTransitionProgressProvider
private val animatorDurationScaleListenerCaptor =
- ArgumentCaptor.forClass(ContentObserver::class.java)
+ ArgumentCaptor.forClass(ContentObserver::class.java)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ contentResolver = spy(context.contentResolver)
- progressProvider = ScaleAwareTransitionProgressProvider(
- sourceProvider,
- contentResolver
- )
+ progressProvider = ScaleAwareTransitionProgressProvider(sourceProvider, contentResolver)
- verify(contentResolver).registerContentObserver(any(), any(),
- animatorDurationScaleListenerCaptor.capture())
+ verify(contentResolver)
+ .registerContentObserver(any(), any(), animatorDurationScaleListenerCaptor.capture())
progressProvider.addCallback(sinkProvider)
}
@@ -121,12 +116,20 @@
}
private fun setAnimationsEnabled(enabled: Boolean) {
- val durationScale = if (enabled) {
- 1f
- } else {
- 0f
- }
- ValueAnimator.setDurationScale(durationScale)
+ val durationScale =
+ if (enabled) {
+ 1f
+ } else {
+ 0f
+ }
+
+ // It uses [TestableSettingsProvider] and it will be cleared after the test
+ Settings.Global.putString(
+ contentResolver,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ durationScale.toString()
+ )
+
animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
deleted file mode 100644
index 7c7f0e1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ /dev/null
@@ -1,248 +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.user.data.repository
-
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
-
- @Before
- fun setUp() {
- super.setUp(isRefactored = true)
- }
-
- @Test
- fun userSwitcherSettings() = runSelfCancelingTest {
- setUpGlobalSettings(
- isSimpleUserSwitcher = true,
- isAddUsersFromLockscreen = true,
- isUserSwitcherEnabled = true,
- )
- underTest = create(this)
-
- var value: UserSwitcherSettingsModel? = null
- underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
-
- assertUserSwitcherSettings(
- model = value,
- expectedSimpleUserSwitcher = true,
- expectedAddUsersFromLockscreen = true,
- expectedUserSwitcherEnabled = true,
- )
-
- setUpGlobalSettings(
- isSimpleUserSwitcher = false,
- isAddUsersFromLockscreen = true,
- isUserSwitcherEnabled = true,
- )
- assertUserSwitcherSettings(
- model = value,
- expectedSimpleUserSwitcher = false,
- expectedAddUsersFromLockscreen = true,
- expectedUserSwitcherEnabled = true,
- )
- }
-
- @Test
- fun refreshUsers() = runSelfCancelingTest {
- underTest = create(this)
- val initialExpectedValue =
- setUpUsers(
- count = 3,
- selectedIndex = 0,
- )
- var userInfos: List<UserInfo>? = null
- var selectedUserInfo: UserInfo? = null
- underTest.userInfos.onEach { userInfos = it }.launchIn(this)
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(initialExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
- val secondExpectedValue =
- setUpUsers(
- count = 4,
- selectedIndex = 1,
- )
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(secondExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
- val selectedNonGuestUserId = selectedUserInfo?.id
- val thirdExpectedValue =
- setUpUsers(
- count = 2,
- isLastGuestUser = true,
- selectedIndex = 1,
- )
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(thirdExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
- assertThat(selectedUserInfo?.isGuest).isTrue()
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
- }
-
- @Test
- fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
- underTest = create(this)
- val unsortedUsers =
- setUpUsers(
- count = 3,
- selectedIndex = 0,
- isLastGuestUser = true,
- )
- unsortedUsers[0].creationTime = 999
- unsortedUsers[1].creationTime = 900
- unsortedUsers[2].creationTime = 950
- val expectedUsers =
- listOf(
- unsortedUsers[1],
- unsortedUsers[0],
- unsortedUsers[2], // last because this is the guest
- )
- var userInfos: List<UserInfo>? = null
- underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(expectedUsers)
- }
-
- @Test
- fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
- underTest = create(this)
- var selectedUserInfo: UserInfo? = null
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
- setUpUsers(
- count = 2,
- selectedIndex = 0,
- )
- tracker.onProfileChanged()
- assertThat(selectedUserInfo?.id == 0)
- setUpUsers(
- count = 2,
- selectedIndex = 1,
- )
- tracker.onProfileChanged()
- assertThat(selectedUserInfo?.id == 1)
- }
-
- private fun setUpUsers(
- count: Int,
- isLastGuestUser: Boolean = false,
- selectedIndex: Int = 0,
- ): List<UserInfo> {
- val userInfos =
- (0 until count).map { index ->
- createUserInfo(
- index,
- isGuest = isLastGuestUser && index == count - 1,
- )
- }
- whenever(manager.aliveUsers).thenReturn(userInfos)
- tracker.set(userInfos, selectedIndex)
- return userInfos
- }
-
- private fun createUserInfo(
- id: Int,
- isGuest: Boolean,
- ): UserInfo {
- val flags = 0
- return UserInfo(
- id,
- "user_$id",
- /* iconPath= */ "",
- flags,
- if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
- )
- }
-
- private fun setUpGlobalSettings(
- isSimpleUserSwitcher: Boolean = false,
- isAddUsersFromLockscreen: Boolean = false,
- isUserSwitcherEnabled: Boolean = true,
- ) {
- context.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
- true,
- )
- globalSettings.putIntForUser(
- UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
- if (isSimpleUserSwitcher) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- globalSettings.putIntForUser(
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- if (isAddUsersFromLockscreen) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- globalSettings.putIntForUser(
- Settings.Global.USER_SWITCHER_ENABLED,
- if (isUserSwitcherEnabled) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- }
-
- private fun assertUserSwitcherSettings(
- model: UserSwitcherSettingsModel?,
- expectedSimpleUserSwitcher: Boolean,
- expectedAddUsersFromLockscreen: Boolean,
- expectedUserSwitcherEnabled: Boolean,
- ) {
- checkNotNull(model)
- assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
- assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
- assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
- }
-
- /**
- * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
- * is then automatically canceled and cleaned-up.
- */
- private fun runSelfCancelingTest(
- block: suspend CoroutineScope.() -> Unit,
- ) =
- runBlocking(Dispatchers.Main.immediate) {
- val scope = CoroutineScope(coroutineContext + Job())
- block(scope)
- scope.cancel()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index dcea83a..2e527be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,54 +17,263 @@
package com.android.systemui.user.data.repository
+import android.content.pm.UserInfo
+import android.os.UserHandle
import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
-abstract class UserRepositoryImplTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplTest : SysuiTestCase() {
- @Mock protected lateinit var manager: UserManager
- @Mock protected lateinit var controller: UserSwitcherController
+ @Mock private lateinit var manager: UserManager
- protected lateinit var underTest: UserRepositoryImpl
+ private lateinit var underTest: UserRepositoryImpl
- protected lateinit var globalSettings: FakeSettings
- protected lateinit var tracker: FakeUserTracker
- protected lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var globalSettings: FakeSettings
+ private lateinit var tracker: FakeUserTracker
- protected fun setUp(isRefactored: Boolean) {
+ @Before
+ fun setUp() {
MockitoAnnotations.initMocks(this)
globalSettings = FakeSettings()
tracker = FakeUserTracker()
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
}
- protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+ @Test
+ fun userSwitcherSettings() = runSelfCancelingTest {
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = true,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ underTest = create(this)
+
+ var value: UserSwitcherSettingsModel? = null
+ underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = true,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = false,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = false,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+ }
+
+ @Test
+ fun refreshUsers() = runSelfCancelingTest {
+ underTest = create(this)
+ val initialExpectedValue =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ )
+ var userInfos: List<UserInfo>? = null
+ var selectedUserInfo: UserInfo? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(initialExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val secondExpectedValue =
+ setUpUsers(
+ count = 4,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(secondExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val selectedNonGuestUserId = selectedUserInfo?.id
+ val thirdExpectedValue =
+ setUpUsers(
+ count = 2,
+ isLastGuestUser = true,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(thirdExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+ assertThat(selectedUserInfo?.isGuest).isTrue()
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+ }
+
+ @Test
+ fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
+ underTest = create(this)
+ val unsortedUsers =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ isLastGuestUser = true,
+ )
+ unsortedUsers[0].creationTime = 999
+ unsortedUsers[1].creationTime = 900
+ unsortedUsers[2].creationTime = 950
+ val expectedUsers =
+ listOf(
+ unsortedUsers[1],
+ unsortedUsers[0],
+ unsortedUsers[2], // last because this is the guest
+ )
+ var userInfos: List<UserInfo>? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(expectedUsers)
+ }
+
+ private fun setUpUsers(
+ count: Int,
+ isLastGuestUser: Boolean = false,
+ selectedIndex: Int = 0,
+ ): List<UserInfo> {
+ val userInfos =
+ (0 until count).map { index ->
+ createUserInfo(
+ index,
+ isGuest = isLastGuestUser && index == count - 1,
+ )
+ }
+ whenever(manager.aliveUsers).thenReturn(userInfos)
+ tracker.set(userInfos, selectedIndex)
+ return userInfos
+ }
+ @Test
+ fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+ underTest = create(this)
+ var selectedUserInfo: UserInfo? = null
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 0,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id).isEqualTo(0)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 1,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id).isEqualTo(1)
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ isGuest: Boolean,
+ ): UserInfo {
+ val flags = 0
+ return UserInfo(
+ id,
+ "user_$id",
+ /* iconPath= */ "",
+ flags,
+ if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+ )
+ }
+
+ private fun setUpGlobalSettings(
+ isSimpleUserSwitcher: Boolean = false,
+ isAddUsersFromLockscreen: Boolean = false,
+ isUserSwitcherEnabled: Boolean = true,
+ ) {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+ true,
+ )
+ globalSettings.putIntForUser(
+ UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+ if (isSimpleUserSwitcher) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ if (isAddUsersFromLockscreen) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.USER_SWITCHER_ENABLED,
+ if (isUserSwitcherEnabled) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ }
+
+ private fun assertUserSwitcherSettings(
+ model: UserSwitcherSettingsModel?,
+ expectedSimpleUserSwitcher: Boolean,
+ expectedAddUsersFromLockscreen: Boolean,
+ expectedUserSwitcherEnabled: Boolean,
+ ) {
+ checkNotNull(model)
+ assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+ assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+ assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+ }
+
+ /**
+ * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+ * is then automatically canceled and cleaned-up.
+ */
+ private fun runSelfCancelingTest(
+ block: suspend CoroutineScope.() -> Unit,
+ ) =
+ runBlocking(Dispatchers.Main.immediate) {
+ val scope = CoroutineScope(coroutineContext + Job())
+ block(scope)
+ scope.cancel()
+ }
+
+ private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
return UserRepositoryImpl(
appContext = context,
manager = manager,
- controller = controller,
applicationScope = scope,
mainDispatcher = IMMEDIATE,
backgroundDispatcher = IMMEDIATE,
globalSettings = globalSettings,
tracker = tracker,
- featureFlags = featureFlags,
)
}
companion object {
- @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
+ @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
deleted file mode 100644
index a363a03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ /dev/null
@@ -1,209 +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.user.data.repository
-
-import android.content.pm.UserInfo
-import androidx.test.filters.SmallTest
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-
- @Captor
- private lateinit var userSwitchCallbackCaptor:
- ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
-
- @Before
- fun setUp() {
- super.setUp(isRefactored = false)
-
- whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
- whenever(controller.isGuestUserAutoCreated).thenReturn(false)
- whenever(controller.isGuestUserResetting).thenReturn(false)
-
- underTest = create()
- }
-
- @Test
- fun `users - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `users - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `users - does not include actions`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
- )
- )
- var models: List<UserModel>? = null
- val job = underTest.users.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(3)
- assertThat(models?.get(0)?.id).isEqualTo(0)
- assertThat(models?.get(0)?.isSelected).isTrue()
- assertThat(models?.get(1)?.id).isEqualTo(1)
- assertThat(models?.get(1)?.isSelected).isFalse()
- assertThat(models?.get(2)?.id).isEqualTo(2)
- assertThat(models?.get(2)?.isSelected).isFalse()
- job.cancel()
- }
-
- @Test
- fun selectedUser() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createUserRecord(1),
- createUserRecord(2),
- )
- )
- var id: Int? = null
- val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
- assertThat(id).isEqualTo(0)
-
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0),
- createUserRecord(1),
- createUserRecord(2, isSelected = true),
- )
- )
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
- userSwitchCallbackCaptor.value.onUserSwitched()
- assertThat(id).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun `actions - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `actions - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `actions - does not include users`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
- )
- )
- var models: List<UserActionModel>? = null
- val job = underTest.actions.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(4)
- assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
- assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
- assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
- assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- job.cancel()
- }
-
- private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
- return UserRecord(
- info = UserInfo(id, "name$id", 0),
- isCurrent = isSelected,
- )
- }
-
- private fun createActionRecord(action: UserActionModel): UserRecord {
- return UserRecord(
- isAddUser = action == UserActionModel.ADD_USER,
- isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
- isGuest = action == UserActionModel.ENTER_GUEST_MODE,
- isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
deleted file mode 100644
index f682e31..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ /dev/null
@@ -1,740 +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.user.domain.interactor
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.internal.R.drawable.ic_account_circle
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.model.ShowDialogRequestModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.advanceUntilIdle
-import org.junit.Before
-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.Mockito.never
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorRefactoredTest : UserInteractorTest() {
-
- override fun isRefactored(): Boolean {
- return true
- }
-
- @Before
- override fun setUp() {
- super.setUp()
-
- overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
- overrideResource(R.dimen.max_avatar_size, 10)
- overrideResource(
- com.android.internal.R.string.config_supervisedUserCreationPackage,
- SUPERVISED_USER_CREATION_APP_PACKAGE,
- )
- whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
- whenever(manager.canAddMoreUsers(any())).thenReturn(true)
- }
-
- @Test
- fun `onRecordSelected - user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
-
- verify(dialogShower).dismiss()
- verify(activityManager).switchUser(userInfos[1].id)
- Unit
- }
-
- @Test
- fun `onRecordSelected - switch to guest user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(info = userInfos.last()))
-
- verify(activityManager).switchUser(userInfos.last().id)
- Unit
- }
-
- @Test
- fun `onRecordSelected - enter guest mode`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
- whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-
- underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
-
- verify(dialogShower).dismiss()
- verify(manager).createGuest(any())
- Unit
- }
-
- @Test
- fun `onRecordSelected - action`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
-
- verify(dialogShower, never()).dismiss()
- verify(activityStarter).startActivity(any(), anyBoolean())
- }
-
- @Test
- fun `users - switcher enabled`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 3, includeGuest = true)
-
- job.cancel()
- }
-
- @Test
- fun `users - switches to second user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- userRepository.setSelectedUserInfo(userInfos[1])
-
- assertUsers(models = value, count = 2, selectedIndex = 1)
- job.cancel()
- }
-
- @Test
- fun `users - switcher not enabled`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 1)
-
- job.cancel()
- }
-
- @Test
- fun selectedUser() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: UserModel? = null
- val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
- assertUser(value, id = 0, isSelected = true)
-
- userRepository.setSelectedUserInfo(userInfos[1])
- assertUser(value, id = 1, isSelected = true)
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
-
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked user not primary - empty list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[1])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked user is guest - empty list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- assertThat(userInfos[1].isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[1])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
- }
-
- @Test
- fun `actions - device locked add from lockscreen set - full list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(
- UserSwitcherSettingsModel(
- isUserSwitcherEnabled = true,
- isAddUsersFromLockscreen = true,
- )
- )
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `actions - device locked - only guest action and manage user is shown`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(true)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `executeAction - add user - dialog shown`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
- val dialogShower: UserSwitchDialogController.DialogShower = mock()
-
- underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
- assertThat(dialogRequest)
- .isEqualTo(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = userInfos[0].userHandle,
- isKeyguardShowing = false,
- showEphemeralMessage = false,
- dialogShower = dialogShower,
- )
- )
-
- underTest.onDialogShown()
- assertThat(dialogRequest).isNull()
-
- job.cancel()
- }
-
- @Test
- fun `executeAction - add supervised user - starts activity`() =
- runBlocking(IMMEDIATE) {
- underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
- assertThat(intentCaptor.value.action)
- .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
- assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
- }
-
- @Test
- fun `executeAction - navigate to manage users`() =
- runBlocking(IMMEDIATE) {
- underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
- assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
- }
-
- @Test
- fun `executeAction - guest mode`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
- whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
- val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
- val showDialogsJob =
- underTest.dialogShowRequests
- .onEach {
- dialogRequests.add(it)
- if (it != null) {
- underTest.onDialogShown()
- }
- }
- .launchIn(this)
- val dismissDialogsJob =
- underTest.dialogDismissRequests
- .onEach {
- if (it != null) {
- underTest.onDialogDismissed()
- }
- }
- .launchIn(this)
-
- underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
- assertThat(dialogRequests)
- .contains(
- ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
- )
- verify(activityManager).switchUser(guestUserInfo.id)
-
- showDialogsJob.cancel()
- dismissDialogsJob.cancel()
- }
-
- @Test
- fun `selectUser - already selected guest re-selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- val guestUserInfo = userInfos[1]
- assertThat(guestUserInfo.isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(guestUserInfo)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(
- newlySelectedUserId = guestUserInfo.id,
- dialogShower = dialogShower,
- )
-
- assertThat(dialogRequest)
- .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
- verify(dialogShower, never()).dismiss()
- job.cancel()
- }
-
- @Test
- fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- val guestUserInfo = userInfos[1]
- assertThat(guestUserInfo.isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(guestUserInfo)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
-
- assertThat(dialogRequest)
- .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
- verify(dialogShower, never()).dismiss()
- job.cancel()
- }
-
- @Test
- fun `selectUser - not currently guest - switches users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
-
- assertThat(dialogRequest).isNull()
- verify(activityManager).switchUser(userInfos[1].id)
- verify(dialogShower).dismiss()
- job.cancel()
- }
-
- @Test
- fun `Telephony call state changes - refreshes users`() =
- runBlocking(IMMEDIATE) {
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- telephonyRepository.setCallState(1)
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `User switched broadcast`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val callback1: UserInteractor.UserCallback = mock()
- val callback2: UserInteractor.UserCallback = mock()
- underTest.addCallback(callback1)
- underTest.addCallback(callback2)
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- userRepository.setSelectedUserInfo(userInfos[1])
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_SWITCHED)
- .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
- )
- }
-
- verify(callback1).onUserStateChanged()
- verify(callback2).onUserStateChanged()
- assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `User info changed broadcast`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_INFO_CHANGED),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `System user unlocked broadcast - refresh users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_UNLOCKED)
- .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `Non-system user unlocked broadcast - do not refresh users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
- }
-
- @Test
- fun userRecords() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
-
- testCoroutineScope.advanceUntilIdle()
-
- assertRecords(
- records = underTest.userRecords.value,
- userIds = listOf(0, 1, 2),
- selectedUserIndex = 0,
- includeGuest = false,
- expectedActions =
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- ),
- )
- }
-
- @Test
- fun selectedUserRecord() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
-
- assertRecordForUser(
- record = underTest.selectedUserRecord.value,
- id = 0,
- hasPicture = true,
- isCurrent = true,
- isSwitchToEnabled = true,
- )
- }
-
- private fun assertUsers(
- models: List<UserModel>?,
- count: Int,
- selectedIndex: Int = 0,
- includeGuest: Boolean = false,
- ) {
- checkNotNull(models)
- assertThat(models.size).isEqualTo(count)
- models.forEachIndexed { index, model ->
- assertUser(
- model = model,
- id = index,
- isSelected = index == selectedIndex,
- isGuest = includeGuest && index == count - 1
- )
- }
- }
-
- private fun assertUser(
- model: UserModel?,
- id: Int,
- isSelected: Boolean = false,
- isGuest: Boolean = false,
- ) {
- checkNotNull(model)
- assertThat(model.id).isEqualTo(id)
- assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
- assertThat(model.isSelected).isEqualTo(isSelected)
- assertThat(model.isSelectable).isTrue()
- assertThat(model.isGuest).isEqualTo(isGuest)
- }
-
- private fun assertRecords(
- records: List<UserRecord>,
- userIds: List<Int>,
- selectedUserIndex: Int = 0,
- includeGuest: Boolean = false,
- expectedActions: List<UserActionModel> = emptyList(),
- ) {
- assertThat(records.size >= userIds.size).isTrue()
- userIds.indices.forEach { userIndex ->
- val record = records[userIndex]
- assertThat(record.info).isNotNull()
- val isGuest = includeGuest && userIndex == userIds.size - 1
- assertRecordForUser(
- record = record,
- id = userIds[userIndex],
- hasPicture = !isGuest,
- isCurrent = userIndex == selectedUserIndex,
- isGuest = isGuest,
- isSwitchToEnabled = true,
- )
- }
-
- assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
- (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
- val record = records[actionIndex]
- assertThat(record.info).isNull()
- assertRecordForAction(
- record = record,
- type = expectedActions[actionIndex - userIds.size],
- )
- }
- }
-
- private fun assertRecordForUser(
- record: UserRecord?,
- id: Int? = null,
- hasPicture: Boolean = false,
- isCurrent: Boolean = false,
- isGuest: Boolean = false,
- isSwitchToEnabled: Boolean = false,
- ) {
- checkNotNull(record)
- assertThat(record.info?.id).isEqualTo(id)
- assertThat(record.picture != null).isEqualTo(hasPicture)
- assertThat(record.isCurrent).isEqualTo(isCurrent)
- assertThat(record.isGuest).isEqualTo(isGuest)
- assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
- }
-
- private fun assertRecordForAction(
- record: UserRecord,
- type: UserActionModel,
- ) {
- assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
- assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
- assertThat(record.isAddSupervisedUser)
- .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
- }
-
- private fun createUserInfos(
- count: Int,
- includeGuest: Boolean,
- ): List<UserInfo> {
- return (0 until count).map { index ->
- val isGuest = includeGuest && index == count - 1
- createUserInfo(
- id = index,
- name =
- if (isGuest) {
- "guest"
- } else {
- "user_$index"
- },
- isPrimary = !isGuest && index == 0,
- isGuest = isGuest,
- )
- }
- }
-
- private fun createUserInfo(
- id: Int,
- name: String,
- isPrimary: Boolean = false,
- isGuest: Boolean = false,
- ): UserInfo {
- return UserInfo(
- id,
- name,
- /* iconPath= */ "",
- /* flags= */ if (isPrimary) {
- UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
- } else {
- 0
- },
- if (isGuest) {
- UserManager.USER_TYPE_FULL_GUEST
- } else {
- UserManager.USER_TYPE_FULL_SYSTEM
- },
- )
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
- private val GUEST_ICON: Drawable = mock()
- private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 58f5531..47efcd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,11 +19,23 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -31,40 +43,76 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+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.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-abstract class UserInteractorTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorTest : SysuiTestCase() {
- @Mock protected lateinit var controller: UserSwitcherController
- @Mock protected lateinit var activityStarter: ActivityStarter
- @Mock protected lateinit var manager: UserManager
- @Mock protected lateinit var activityManager: ActivityManager
- @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
- @Mock protected lateinit var uiEventLogger: UiEventLogger
- @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
- protected lateinit var underTest: UserInteractor
+ private lateinit var underTest: UserInteractor
- protected lateinit var testCoroutineScope: TestCoroutineScope
- protected lateinit var userRepository: FakeUserRepository
- protected lateinit var keyguardRepository: FakeKeyguardRepository
- protected lateinit var telephonyRepository: FakeTelephonyRepository
+ private lateinit var testCoroutineScope: TestCoroutineScope
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var telephonyRepository: FakeTelephonyRepository
+ private lateinit var featureFlags: FakeFeatureFlags
- abstract fun isRefactored(): Boolean
-
- open fun setUp() {
+ @Before
+ fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+ overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+ overrideResource(R.dimen.max_avatar_size, 10)
+ overrideResource(
+ com.android.internal.R.string.config_supervisedUserCreationPackage,
+ SUPERVISED_USER_CREATION_APP_PACKAGE,
+ )
+
+ featureFlags = FakeFeatureFlags()
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
@@ -79,16 +127,11 @@
UserInteractor(
applicationContext = context,
repository = userRepository,
- controller = controller,
activityStarter = activityStarter,
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
),
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
- },
manager = manager,
applicationScope = testCoroutineScope,
telephonyInteractor =
@@ -113,11 +156,794 @@
uiEventLogger = uiEventLogger,
resumeSessionReceiver = resumeSessionReceiver,
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
- )
+ ),
+ featureFlags = featureFlags,
)
}
+ @Test
+ fun `onRecordSelected - user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(activityManager).switchUser(userInfos[1].id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - switch to guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+ verify(activityManager).switchUser(userInfos.last().id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - enter guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+ underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(manager).createGuest(any())
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+ verify(dialogShower, never()).dismiss()
+ verify(activityStarter).startActivity(any(), anyBoolean())
+ }
+
+ @Test
+ fun `users - switcher enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 3, includeGuest = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switches to second user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertUsers(models = value, count = 2, selectedIndex = 1)
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switcher not enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun selectedUser() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: UserModel? = null
+ val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+ assertUser(value, id = 0, isSelected = true)
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ assertUser(value, id = 1, isSelected = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user not primary - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user is guest - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ assertThat(userInfos[1].isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked add from lockscreen set - full list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true,
+ )
+ )
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked - only guest action and manage user is shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(true)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add user - dialog shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+ underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+ assertThat(dialogRequest)
+ .isEqualTo(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = userInfos[0].userHandle,
+ isKeyguardShowing = false,
+ showEphemeralMessage = false,
+ dialogShower = dialogShower,
+ )
+ )
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add supervised user - starts activity`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+ assertThat(intentCaptor.value.action)
+ .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+ }
+
+ @Test
+ fun `executeAction - navigate to manage users`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+ }
+
+ @Test
+ fun `executeAction - guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+ val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+ val showDialogsJob =
+ underTest.dialogShowRequests
+ .onEach {
+ dialogRequests.add(it)
+ if (it != null) {
+ underTest.onDialogShown()
+ }
+ }
+ .launchIn(this)
+ val dismissDialogsJob =
+ underTest.dialogDismissRequests
+ .onEach {
+ if (it != null) {
+ underTest.onDialogDismissed()
+ }
+ }
+ .launchIn(this)
+
+ underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+ assertThat(dialogRequests)
+ .contains(
+ ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+ )
+ verify(activityManager).switchUser(guestUserInfo.id)
+
+ showDialogsJob.cancel()
+ dismissDialogsJob.cancel()
+ }
+
+ @Test
+ fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(
+ newlySelectedUserId = guestUserInfo.id,
+ dialogShower = dialogShower,
+ )
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - not currently guest - switches users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest).isNull()
+ verify(activityManager).switchUser(userInfos[1].id)
+ verify(dialogShower).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `Telephony call state changes - refreshes users`() =
+ runBlocking(IMMEDIATE) {
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ telephonyRepository.setCallState(1)
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User switched broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val callback1: UserInteractor.UserCallback = mock()
+ val callback2: UserInteractor.UserCallback = mock()
+ underTest.addCallback(callback1)
+ underTest.addCallback(callback2)
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_SWITCHED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+ )
+ }
+
+ verify(callback1).onUserStateChanged()
+ verify(callback2).onUserStateChanged()
+ assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User info changed broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_INFO_CHANGED),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `System user unlocked broadcast - refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `Non-system user unlocked broadcast - do not refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+ }
+
+ @Test
+ fun userRecords() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ testCoroutineScope.advanceUntilIdle()
+
+ assertRecords(
+ records = underTest.userRecords.value,
+ userIds = listOf(0, 1, 2),
+ selectedUserIndex = 0,
+ includeGuest = false,
+ expectedActions =
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ ),
+ )
+ }
+
+ @Test
+ fun selectedUserRecord() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertRecordForUser(
+ record = underTest.selectedUserRecord.value,
+ id = 0,
+ hasPicture = true,
+ isCurrent = true,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ @Test
+ fun `users - secondary user - no guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserModel>? = null
+ val job = underTest.users.onEach { res = it }.launchIn(this)
+ assertThat(res?.size == 2).isTrue()
+ assertThat(res?.find { it.isGuest }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest user record`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserRecord>? = null
+ val job = underTest.userRecords.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it.isGuest }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `show user switcher - full screen disabled - shows dialog switcher`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+
+ var dialogRequest: ShowDialogRequestModel? = null
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ // Dialog is shown.
+ assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog)
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `show user switcher - full screen enabled - launches activity`() {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ // Dialog is shown.
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .startActivity(
+ intentCaptor.capture(),
+ /* dismissShade= */ eq(true),
+ /* ActivityLaunchAnimator.Controller= */ nullable(),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ eq(UserHandle.SYSTEM),
+ )
+ assertThat(intentCaptor.value.component)
+ .isEqualTo(
+ ComponentName(
+ context,
+ UserSwitcherActivity::class.java,
+ )
+ )
+ }
+
+ @Test
+ fun `users - secondary user - managed profile is not included`() =
+ runBlocking(IMMEDIATE) {
+ var userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ userInfos.add(
+ UserInfo(
+ 50,
+ "Work Profile",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_MANAGED_PROFILE
+ )
+ )
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserModel>? = null
+ val job = underTest.users.onEach { res = it }.launchIn(this)
+ assertThat(res?.size == 3).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun `current user is not primary and user switcher is disabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+ var selectedUser: UserModel? = null
+ val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+ assertThat(selectedUser).isNotNull()
+ job.cancel()
+ }
+
+ private fun assertUsers(
+ models: List<UserModel>?,
+ count: Int,
+ selectedIndex: Int = 0,
+ includeGuest: Boolean = false,
+ ) {
+ checkNotNull(models)
+ assertThat(models.size).isEqualTo(count)
+ models.forEachIndexed { index, model ->
+ assertUser(
+ model = model,
+ id = index,
+ isSelected = index == selectedIndex,
+ isGuest = includeGuest && index == count - 1
+ )
+ }
+ }
+
+ private fun assertUser(
+ model: UserModel?,
+ id: Int,
+ isSelected: Boolean = false,
+ isGuest: Boolean = false,
+ ) {
+ checkNotNull(model)
+ assertThat(model.id).isEqualTo(id)
+ assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+ assertThat(model.isSelected).isEqualTo(isSelected)
+ assertThat(model.isSelectable).isTrue()
+ assertThat(model.isGuest).isEqualTo(isGuest)
+ }
+
+ private fun assertRecords(
+ records: List<UserRecord>,
+ userIds: List<Int>,
+ selectedUserIndex: Int = 0,
+ includeGuest: Boolean = false,
+ expectedActions: List<UserActionModel> = emptyList(),
+ ) {
+ assertThat(records.size >= userIds.size).isTrue()
+ userIds.indices.forEach { userIndex ->
+ val record = records[userIndex]
+ assertThat(record.info).isNotNull()
+ val isGuest = includeGuest && userIndex == userIds.size - 1
+ assertRecordForUser(
+ record = record,
+ id = userIds[userIndex],
+ hasPicture = !isGuest,
+ isCurrent = userIndex == selectedUserIndex,
+ isGuest = isGuest,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+ (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+ val record = records[actionIndex]
+ assertThat(record.info).isNull()
+ assertRecordForAction(
+ record = record,
+ type = expectedActions[actionIndex - userIds.size],
+ )
+ }
+ }
+
+ private fun assertRecordForUser(
+ record: UserRecord?,
+ id: Int? = null,
+ hasPicture: Boolean = false,
+ isCurrent: Boolean = false,
+ isGuest: Boolean = false,
+ isSwitchToEnabled: Boolean = false,
+ ) {
+ checkNotNull(record)
+ assertThat(record.info?.id).isEqualTo(id)
+ assertThat(record.picture != null).isEqualTo(hasPicture)
+ assertThat(record.isCurrent).isEqualTo(isCurrent)
+ assertThat(record.isGuest).isEqualTo(isGuest)
+ assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+ }
+
+ private fun assertRecordForAction(
+ record: UserRecord,
+ type: UserActionModel,
+ ) {
+ assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+ assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+ assertThat(record.isAddSupervisedUser)
+ .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+ }
+
+ private fun createUserInfos(
+ count: Int,
+ includeGuest: Boolean,
+ ): List<UserInfo> {
+ return (0 until count).map { index ->
+ val isGuest = includeGuest && index == count - 1
+ createUserInfo(
+ id = index,
+ name =
+ if (isGuest) {
+ "guest"
+ } else {
+ "user_$index"
+ },
+ isPrimary = !isGuest && index == 0,
+ isGuest = isGuest,
+ )
+ }
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ name: String,
+ isPrimary: Boolean = false,
+ isGuest: Boolean = false,
+ ): UserInfo {
+ return UserInfo(
+ id,
+ name,
+ /* iconPath= */ "",
+ /* flags= */ if (isPrimary) {
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
+ } else {
+ UserInfo.FLAG_FULL
+ },
+ if (isGuest) {
+ UserManager.USER_TYPE_FULL_GUEST
+ } else {
+ UserManager.USER_TYPE_FULL_SYSTEM
+ },
+ )
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
+ private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val GUEST_ICON: Drawable = mock()
+ private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
deleted file mode 100644
index 6a17c8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ /dev/null
@@ -1,174 +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.user.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-open class UserInteractorUnrefactoredTest : UserInteractorTest() {
-
- override fun isRefactored(): Boolean {
- return false
- }
-
- @Before
- override fun setUp() {
- super.setUp()
- }
-
- @Test
- fun `actions - not actionable when locked and locked - no actions`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions).isEmpty()
- job.cancel()
- }
-
- @Test
- fun `actions - not actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun selectUser() {
- val userId = 3
-
- underTest.selectUser(userId)
-
- verify(controller).onUserSelected(eq(userId), nullable())
- }
-
- @Test
- fun `executeAction - guest`() {
- underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
- verify(controller).createAndSwitchToGuestUser(nullable())
- }
-
- @Test
- fun `executeAction - add user`() {
- underTest.executeAction(UserActionModel.ADD_USER)
-
- verify(controller).showAddUserDialog(nullable())
- }
-
- @Test
- fun `executeAction - add supervised user`() {
- underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
- verify(controller).startSupervisedUserActivity()
- }
-
- @Test
- fun `executeAction - manage users`() {
- underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
- verify(activityStarter).startActivity(any(), anyBoolean())
- }
-
- private fun setActions() {
- userRepository.setActions(UserActionModel.values().toList())
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
new file mode 100644
index 0000000..795ff17
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -0,0 +1,306 @@
+/*
+ * 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.user.ui.viewmodel
+
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StatusBarUserChipViewModelTest : SysuiTestCase() {
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+
+ private lateinit var underTest: StatusBarUserChipViewModel
+
+ private val userRepository = FakeUserRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val featureFlags = FakeFeatureFlags()
+ private lateinit var guestUserInteractor: GuestUserInteractor
+ private lateinit var refreshUsersScheduler: RefreshUsersScheduler
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ doAnswer { invocation ->
+ val userId = invocation.arguments[0] as Int
+ when (userId) {
+ USER_ID_0 -> return@doAnswer USER_IMAGE_0
+ USER_ID_1 -> return@doAnswer USER_IMAGE_1
+ USER_ID_2 -> return@doAnswer USER_IMAGE_2
+ else -> return@doAnswer mock<Bitmap>()
+ }
+ }
+ .`when`(manager)
+ .getUserIcon(anyInt())
+
+ userRepository.isStatusBarUserChipEnabled = true
+
+ refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ repository = userRepository,
+ )
+ guestUserInteractor =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
+ manager = manager,
+ repository = userRepository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+ )
+
+ underTest = viewModel()
+ }
+
+ @Test
+ fun `config is false - chip is disabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = false
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isFalse()
+ }
+
+ @Test
+ fun `config is true - chip is enabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = true
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isTrue()
+ }
+
+ @Test
+ fun `should show chip criteria - single user`() =
+ testScope.runTest {
+ userRepository.setUserInfos(listOf(USER_0))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ val values = mutableListOf<Boolean>()
+
+ val job = launch { underTest.isChipVisible.toList(values) }
+ advanceUntilIdle()
+
+ assertThat(values).containsExactly(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `should show chip criteria - multiple users`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Boolean? = null
+ val job = underTest.isChipVisible.onEach { latest = it }.launchIn(this)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip name - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Text? = null
+ val job = underTest.userName.onEach { latest = it }.launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_NAME_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_NAME_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_NAME_2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip avatar - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ // A little hacky. System server passes us bitmaps and we wrap them in the interactor.
+ // Unwrap them to make sure we're always tracking the current user's bitmap
+ var latest: Bitmap? = null
+ val job =
+ underTest.userAvatar
+ .onEach {
+ if (it !is BitmapDrawable) {
+ latest = null
+ }
+
+ latest = (it as BitmapDrawable).bitmap
+ }
+ .launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_IMAGE_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_IMAGE_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_IMAGE_2)
+
+ job.cancel()
+ }
+
+ private fun viewModel(): StatusBarUserChipViewModel {
+ return StatusBarUserChipViewModel(
+ context = context,
+ interactor =
+ UserInteractor(
+ applicationContext = context,
+ repository = userRepository,
+ activityStarter = activityStarter,
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ ),
+ featureFlags = featureFlags,
+ manager = manager,
+ applicationScope = testScope.backgroundScope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = FakeTelephonyRepository(),
+ ),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ backgroundDispatcher = testDispatcher,
+ activityManager = activityManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ guestUserInteractor = guestUserInteractor,
+ )
+ )
+ }
+
+ private suspend fun setMultipleUsers() {
+ userRepository.setUserInfos(listOf(USER_0, USER_1, USER_2))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ }
+
+ companion object {
+ private const val USER_ID_0 = 0
+ private val USER_IMAGE_0 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_0 = Text.Loaded("zero")
+
+ private const val USER_ID_1 = 1
+ private val USER_IMAGE_1 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_1 = Text.Loaded("one")
+
+ private const val USER_ID_2 = 2
+ private val USER_IMAGE_2 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_2 = Text.Loaded("two")
+
+ private val USER_0 =
+ UserInfo(
+ USER_ID_0,
+ USER_NAME_0.text!!,
+ /* iconPath */ "",
+ /* flags */ UserInfo.FLAG_FULL,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_1 =
+ UserInfo(
+ USER_ID_1,
+ USER_NAME_1.text!!,
+ /* iconPath */ "",
+ /* flags */ UserInfo.FLAG_FULL,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_2 =
+ UserInfo(
+ USER_ID_2,
+ USER_NAME_2.text!!,
+ /* iconPath */ "",
+ /* flags */ UserInfo.FLAG_FULL,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 116023a..1730b75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -19,7 +19,7 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
-import android.graphics.drawable.Drawable
+import android.content.pm.UserInfo
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
@@ -28,31 +28,37 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestResult
+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
@@ -60,11 +66,11 @@
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserSwitcherViewModelTest : SysuiTestCase() {
- @Mock private lateinit var controller: UserSwitcherController
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var manager: UserManager
@@ -80,28 +86,47 @@
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var powerRepository: FakePowerRepository
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+ private lateinit var injectedScope: CoroutineScope
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+ whenever(manager.getUserSwitchability(any()))
+ .thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
+ overrideResource(
+ com.android.internal.R.string.config_supervisedUserCreationPackage,
+ SUPERVISED_USER_CREATION_PACKAGE,
+ )
+ testDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
userRepository = FakeUserRepository()
+ runBlocking {
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ )
+ )
+ }
+
keyguardRepository = FakeKeyguardRepository()
powerRepository = FakePowerRepository()
- val featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
- val scope = TestCoroutineScope()
val refreshUsersScheduler =
RefreshUsersScheduler(
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
+ applicationScope = injectedScope,
+ mainDispatcher = testDispatcher,
repository = userRepository,
)
val guestUserInteractor =
GuestUserInteractor(
applicationContext = context,
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
- backgroundDispatcher = IMMEDIATE,
+ applicationScope = injectedScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -118,21 +143,20 @@
UserInteractor(
applicationContext = context,
repository = userRepository,
- controller = controller,
activityStarter = activityStarter,
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
),
- featureFlags = featureFlags,
+ featureFlags = FakeFeatureFlags(),
manager = manager,
- applicationScope = scope,
+ applicationScope = injectedScope,
telephonyInteractor =
TelephonyInteractor(
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
- backgroundDispatcher = IMMEDIATE,
+ backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
@@ -141,222 +165,216 @@
PowerInteractor(
repository = powerRepository,
),
- featureFlags = featureFlags,
guestUserInteractor = guestUserInteractor,
)
.create(UserSwitcherViewModel::class.java)
}
@Test
- fun users() =
- runBlocking(IMMEDIATE) {
- userRepository.setUsers(
+ fun users() = selfCancelingTest {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 2,
+ /* name= */ "two",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ )
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+ assertThat(userViewModels.last()).hasSize(3)
+ assertUserViewModel(
+ viewModel = userViewModels.last()[0],
+ viewKey = 0,
+ name = "zero",
+ isSelectionMarkerVisible = true,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[1],
+ viewKey = 1,
+ name = "one",
+ isSelectionMarkerVisible = false,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[2],
+ viewKey = 2,
+ name = "two",
+ isSelectionMarkerVisible = false,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - few users`() = selfCancelingTest {
+ setUsers(count = 2)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - many users`() = selfCancelingTest {
+ setUsers(count = 5)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(3)
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest {
+ setUsers(2)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest {
+ val userInfos = setUsers(2)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(false)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isFalse()
+ job.cancel()
+ }
+
+ @Test
+ fun menu() = selfCancelingTest {
+ val isMenuVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
+ assertThat(isMenuVisible.last()).isFalse()
+
+ underTest.onOpenMenuButtonClicked()
+ assertThat(isMenuVisible.last()).isTrue()
+
+ underTest.onMenuClosed()
+ assertThat(isMenuVisible.last()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `menu actions`() = selfCancelingTest {
+ setUsers(2)
+ val actions = mutableListOf<List<UserActionViewModel>>()
+ val job = launch(testDispatcher) { underTest.menu.toList(actions) }
+
+ assertThat(actions.last().map { it.viewKey })
+ .isEqualTo(
listOf(
- UserModel(
- id = 0,
- name = Text.Loaded("zero"),
- image = USER_IMAGE,
- isSelected = true,
- isSelectable = true,
- isGuest = false,
- ),
- UserModel(
- id = 1,
- name = Text.Loaded("one"),
- image = USER_IMAGE,
- isSelected = false,
- isSelectable = true,
- isGuest = false,
- ),
- UserModel(
- id = 2,
- name = Text.Loaded("two"),
- image = USER_IMAGE,
- isSelected = false,
- isSelectable = false,
- isGuest = false,
- ),
+ UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+ UserActionModel.ADD_USER.ordinal.toLong(),
+ UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
)
)
- var userViewModels: List<UserViewModel>? = null
- val job = underTest.users.onEach { userViewModels = it }.launchIn(this)
-
- assertThat(userViewModels).hasSize(3)
- assertUserViewModel(
- viewModel = userViewModels?.get(0),
- viewKey = 0,
- name = "zero",
- isSelectionMarkerVisible = true,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
- isClickable = true,
- )
- assertUserViewModel(
- viewModel = userViewModels?.get(1),
- viewKey = 1,
- name = "one",
- isSelectionMarkerVisible = false,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
- isClickable = true,
- )
- assertUserViewModel(
- viewModel = userViewModels?.get(2),
- viewKey = 2,
- name = "two",
- isSelectionMarkerVisible = false,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA,
- isClickable = false,
- )
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun `maximumUserColumns - few users`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- var value: Int? = null
- val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+ fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest {
+ val userInfos = setUsers(count = 2)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(value).isEqualTo(4)
- job.cancel()
- }
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
@Test
- fun `maximumUserColumns - many users`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 5)
- var value: Int? = null
- val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+ fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(value).isEqualTo(3)
- job.cancel()
- }
+ powerRepository.setInteractive(false)
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
@Test
- fun `isOpenMenuButtonVisible - has actions - true`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
+ fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- var isVisible: Boolean? = null
- val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+ underTest.onCancelButtonClicked()
- assertThat(isVisible).isTrue()
- job.cancel()
- }
+ assertThat(isFinishRequested.last()).isTrue()
- @Test
- fun `isOpenMenuButtonVisible - no actions - false`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(emptyList())
+ underTest.onFinished()
- var isVisible: Boolean? = null
- val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(isVisible).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
- @Test
- fun menu() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- var isMenuVisible: Boolean? = null
- val job = underTest.isMenuVisible.onEach { isMenuVisible = it }.launchIn(this)
- assertThat(isMenuVisible).isFalse()
-
- underTest.onOpenMenuButtonClicked()
- assertThat(isMenuVisible).isTrue()
-
- underTest.onMenuClosed()
- assertThat(isMenuVisible).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun `menu actions`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- var actions: List<UserActionViewModel>? = null
- val job = underTest.menu.onEach { actions = it }.launchIn(this)
-
- assertThat(actions?.map { it.viewKey })
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
- UserActionModel.ADD_USER.ordinal.toLong(),
- UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when user is switched`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- userRepository.setSelectedUser(1)
- yield()
- assertThat(isFinishRequested).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when the screen turns off`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- powerRepository.setInteractive(false)
- yield()
- assertThat(isFinishRequested).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when cancel button is clicked`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- underTest.onCancelButtonClicked()
- yield()
- assertThat(isFinishRequested).isTrue()
-
- underTest.onFinished()
- yield()
- assertThat(isFinishRequested).isFalse()
-
- job.cancel()
- }
-
- private suspend fun setUsers(count: Int) {
- userRepository.setUsers(
+ private suspend fun setUsers(count: Int): List<UserInfo> {
+ val userInfos =
(0 until count).map { index ->
- UserModel(
- id = index,
- name = Text.Loaded("$index"),
- image = USER_IMAGE,
- isSelected = index == 0,
- isSelectable = true,
- isGuest = false,
+ UserInfo(
+ /* id= */ index,
+ /* name= */ "$index",
+ /* iconPath= */ "",
+ /* flags= */ if (index == 0) {
+ // This is the primary user.
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
+ } else {
+ // This isn't the primary user.
+ UserInfo.FLAG_FULL
+ },
+ UserManager.USER_TYPE_FULL_SYSTEM,
)
}
- )
+ userRepository.setUserInfos(userInfos)
+
+ if (userInfos.isNotEmpty()) {
+ userRepository.setSelectedUserInfo(userInfos[0])
+ }
+ return userInfos
}
private fun assertUserViewModel(
@@ -364,19 +382,25 @@
viewKey: Int,
name: String,
isSelectionMarkerVisible: Boolean,
- alpha: Float,
- isClickable: Boolean,
) {
checkNotNull(viewModel)
assertThat(viewModel.viewKey).isEqualTo(viewKey)
assertThat(viewModel.name).isEqualTo(Text.Loaded(name))
assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
- assertThat(viewModel.alpha).isEqualTo(alpha)
- assertThat(viewModel.onClicked != null).isEqualTo(isClickable)
+ assertThat(viewModel.alpha)
+ .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
+ assertThat(viewModel.onClicked).isNotNull()
}
+ private fun selfCancelingTest(
+ block: suspend TestScope.() -> Unit,
+ ): TestResult =
+ testScope.runTest {
+ block()
+ injectedScope.coroutineContext[Job.Key]?.cancelAndJoin()
+ }
+
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private val USER_IMAGE = mock<Drawable>()
+ private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 0b53133..2878864 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -141,4 +141,158 @@
mCondition.clearCondition();
assertThat(mCondition.isConditionSet()).isFalse();
}
+
+ @Test
+ public void combineConditionsWithOr_allFalse_reportsNotMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ false));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_allTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ true));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_singleTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ true));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_unknownAndTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_unknownAndFalse_reportsNotMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isFalse();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, never()).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_allFalse_reportsNotMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ false));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_allTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ true));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_singleTrue_reportsNotMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ false));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_unknownAndTrue_reportsNotMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isFalse();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, never()).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_unknownAndFalse_reportsMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index 0d8dd2c..df08efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -28,8 +28,8 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 2e74bf5..a0b4eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -18,6 +18,7 @@
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +29,7 @@
import android.app.KeyguardManager;
import android.media.AudioManager;
import android.os.SystemClock;
+import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.InputDevice;
@@ -38,6 +40,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -49,6 +52,9 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -71,6 +77,8 @@
View mDrawerVibrate;
View mDrawerMute;
View mDrawerNormal;
+ private DeviceConfigProxyFake mDeviceConfigProxy;
+ private FakeExecutor mExecutor;
@Mock
VolumeDialogController mVolumeDialogController;
@@ -97,6 +105,9 @@
getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
+ mDeviceConfigProxy = new DeviceConfigProxyFake();
+ mExecutor = new FakeExecutor(new FakeSystemClock());
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -106,7 +117,9 @@
mMediaOutputDialogFactory,
mVolumePanelFactory,
mActivityStarter,
- mInteractionJankMonitor);
+ mInteractionJankMonitor,
+ mDeviceConfigProxy,
+ mExecutor);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -123,6 +136,9 @@
VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
+
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
}
private State createShellState() {
@@ -292,6 +308,35 @@
AudioManager.RINGER_MODE_NORMAL, false);
}
+ /**
+ * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
+ * API does not exist. So we do the next best thing; we check the cached icon id.
+ */
+ @Test
+ public void notificationVolumeSeparated_theRingerIconChanges() {
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
+ mExecutor.runAllReady(); // for the config change to take effect
+
+ // assert icon is new based on res id
+ assertEquals(mDialog.mVolumeRingerIconDrawableId,
+ R.drawable.ic_speaker_on);
+ assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
+ R.drawable.ic_speaker_mute);
+ }
+
+ @Test
+ public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+ mExecutor.runAllReady();
+
+ assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
+ assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index bee882d..db3b9b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,6 +77,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -157,6 +158,7 @@
import java.util.List;
import java.util.Optional;
+@FlakyTest
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -1329,7 +1331,7 @@
spyOn(mContext);
mBubbleController.updateBubble(mBubbleEntry);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
@@ -1349,7 +1351,7 @@
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1363,7 +1365,7 @@
mBubbleData.setExpanded(true);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
i.putExtra("reason", "gestureNav");
mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1377,7 +1379,7 @@
mBubbleData.setExpanded(true);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index a35427f..6c82cef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -27,7 +27,7 @@
private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
init {
- Flags.flagFields.forEach { entry: Map.Entry<String, Flag<*>> ->
+ FlagsFactory.knownFlags.forEach { entry: Map.Entry<String, Flag<*>> ->
knownFlagNames[entry.value.id] = entry.key
}
}
@@ -87,8 +87,6 @@
override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
- override fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean = requireBooleanValue(flag.id)
-
override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 627bd09..3601667 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import kotlinx.coroutines.flow.Flow
@@ -44,18 +45,29 @@
private val _isDozing = MutableStateFlow(false)
override val isDozing: Flow<Boolean> = _isDozing
+ private val _isDreaming = MutableStateFlow(false)
+ override val isDreaming: Flow<Boolean> = _isDreaming
+
private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
override val statusBarState: Flow<StatusBarState> = _statusBarState
+ private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
+ override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
+
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+ private val _isUdfpsSupported = MutableStateFlow(false)
+
private val _isBouncerShowing = MutableStateFlow(false)
override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+ private val _isKeyguardGoingAway = MutableStateFlow(false)
+ override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway
+
private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
@@ -86,4 +98,8 @@
fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount
}
+
+ override fun isUdfpsSupported(): Boolean {
+ return _isUdfpsSupported.value
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 325da4e..63448e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -28,8 +28,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -43,7 +41,6 @@
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.security.data.repository.SecurityRepositoryImpl
import com.android.systemui.settings.FakeUserTracker
@@ -54,6 +51,7 @@
import com.android.systemui.statusbar.policy.SecurityController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
@@ -97,13 +95,12 @@
/** Create a [FooterActionsInteractor] to be used in tests. */
fun footerActionsInteractor(
activityStarter: ActivityStarter = mock(),
- featureFlags: FeatureFlags = FakeFeatureFlags(),
metricsLogger: MetricsLogger = FakeMetricsLogger(),
uiEventLogger: UiEventLogger = UiEventLoggerFake(),
deviceProvisionedController: DeviceProvisionedController = mock(),
qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
fgsManagerController: FgsManagerController = mock(),
- userSwitchDialogController: UserSwitchDialogController = mock(),
+ userInteractor: UserInteractor = mock(),
securityRepository: SecurityRepository = securityRepository(),
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
@@ -112,13 +109,12 @@
): FooterActionsInteractor {
return FooterActionsInteractorImpl(
activityStarter,
- featureFlags,
metricsLogger,
uiEventLogger,
deviceProvisionedController,
qsSecurityFooterUtils,
fgsManagerController,
- userSwitchDialogController,
+ userInteractor,
securityRepository,
foregroundServicesRepository,
userSwitcherRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 4df8aa4..ea5a302 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -20,26 +20,15 @@
import android.content.pm.UserInfo
import android.os.UserHandle
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.yield
class FakeUserRepository : UserRepository {
- private val _users = MutableStateFlow<List<UserModel>>(emptyList())
- override val users: Flow<List<UserModel>> = _users.asStateFlow()
- override val selectedUser: Flow<UserModel> =
- users.map { models -> models.first { model -> model.isSelected } }
-
- private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
- override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
-
private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow()
@@ -52,9 +41,6 @@
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
- private val _isActionableWhenLocked = MutableStateFlow(false)
- override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
-
private var _isGuestUserAutoCreated: Boolean = false
override val isGuestUserAutoCreated: Boolean
get() = _isGuestUserAutoCreated
@@ -63,6 +49,8 @@
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override var isStatusBarUserChipEnabled: Boolean = false
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
@@ -100,35 +88,6 @@
yield()
}
- fun setUsers(models: List<UserModel>) {
- _users.value = models
- }
-
- suspend fun setSelectedUser(userId: Int) {
- check(_users.value.find { it.id == userId } != null) {
- "Cannot select a user with ID $userId - no user with that ID found!"
- }
-
- setUsers(
- _users.value.map { model ->
- when {
- model.isSelected && model.id != userId -> model.copy(isSelected = false)
- !model.isSelected && model.id == userId -> model.copy(isSelected = true)
- else -> model
- }
- }
- )
- yield()
- }
-
- fun setActions(models: List<UserActionModel>) {
- _actions.value = models
- }
-
- fun setActionableWhenLocked(value: Boolean) {
- _isActionableWhenLocked.value = value
- }
-
fun setGuestUserAutoCreated(value: Boolean) {
_isGuestUserAutoCreated = value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 33ece00..21e16a1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -16,7 +16,6 @@
package com.android.systemui.util;
-import android.content.Context;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
@@ -83,7 +82,7 @@
}
@Override
- public void enforceReadPermission(Context context, String namespace) {
+ public void enforceReadPermission(String namespace) {
// no-op
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
index 1353ad2..07ed110 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
@@ -25,15 +25,21 @@
super();
}
- FakeCondition(Boolean initialValue, Boolean overriding) {
+ FakeCondition(Boolean initialValue) {
+ super(initialValue, false);
+ }
+
+ FakeCondition(Boolean initialValue, boolean overriding) {
super(initialValue, overriding);
}
@Override
- public void start() {}
+ public void start() {
+ }
@Override
- public void stop() {}
+ public void stop() {
+ }
public void fakeUpdateCondition(boolean isConditionMet) {
updateCondition(isConditionMet);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
index d245c72..63756c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -18,7 +18,7 @@
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
public class FakePluginManager implements PluginManager {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index dc6a8fb..ec1f352 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -18,7 +18,7 @@
import android.util.ArrayMap;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index 5c92b34..06ca153 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -14,7 +14,6 @@
*/
package com.android.systemui.unfold.util
-import android.animation.ValueAnimator
import android.content.ContentResolver
import android.database.ContentObserver
import android.provider.Settings
@@ -46,13 +45,15 @@
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
/* notifyForDescendants= */ false,
- animatorDurationScaleObserver)
+ animatorDurationScaleObserver
+ )
onAnimatorScaleChanged()
}
private fun onAnimatorScaleChanged() {
- val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
- scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+ scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(
+ contentResolver.areAnimationsEnabled()
+ )
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -74,4 +75,18 @@
progressProvider: UnfoldTransitionProgressProvider
): ScaleAwareTransitionProgressProvider
}
+
+ companion object {
+ fun ContentResolver.areAnimationsEnabled(): Boolean {
+ val animationScale =
+ Settings.Global.getStringForUser(
+ this,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ this.userId
+ )
+ ?.toFloatOrNull()
+ ?: 1f
+ return animationScale != 0f
+ }
+ }
}
diff --git a/packages/VpnDialogs/Android.bp b/packages/VpnDialogs/Android.bp
index 05135b2..e4f80e2 100644
--- a/packages/VpnDialogs/Android.bp
+++ b/packages/VpnDialogs/Android.bp
@@ -23,10 +23,15 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+android_library {
+ name: "VpnDialogsLib",
+ srcs: ["src/**/*.java"],
+}
+
android_app {
name: "VpnDialogs",
certificate: "platform",
privileged: true,
- srcs: ["src/**/*.java"],
+ static_libs: ["VpnDialogsLib"],
platform_apis: true,
}
diff --git a/packages/VpnDialogs/res/values-af/strings.xml b/packages/VpnDialogs/res/values-af/strings.xml
index 88ccbd9..b1aedf3 100644
--- a/packages/VpnDialogs/res/values-af/strings.xml
+++ b/packages/VpnDialogs/res/values-af/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Ontkoppel"</string>
<string name="open_app" msgid="3717639178595958667">"Maak program oop"</string>
<string name="dismiss" msgid="6192859333764711227">"Maak toe"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-am/strings.xml b/packages/VpnDialogs/res/values-am/strings.xml
index 9fc5ff4..d1fcaa5 100644
--- a/packages/VpnDialogs/res/values-am/strings.xml
+++ b/packages/VpnDialogs/res/values-am/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ግንኙነት አቋርጥ"</string>
<string name="open_app" msgid="3717639178595958667">"መተግበሪያን ክፈት"</string>
<string name="dismiss" msgid="6192859333764711227">"አሰናብት"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ar/strings.xml b/packages/VpnDialogs/res/values-ar/strings.xml
index 33be6a3..0e0581e 100644
--- a/packages/VpnDialogs/res/values-ar/strings.xml
+++ b/packages/VpnDialogs/res/values-ar/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"قطع الاتصال"</string>
<string name="open_app" msgid="3717639178595958667">"فتح التطبيق"</string>
<string name="dismiss" msgid="6192859333764711227">"تجاهل"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-as/strings.xml b/packages/VpnDialogs/res/values-as/strings.xml
index 3f2e234..dc99278 100644
--- a/packages/VpnDialogs/res/values-as/strings.xml
+++ b/packages/VpnDialogs/res/values-as/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"সংযোগ বিচ্ছিন্ন কৰক"</string>
<string name="open_app" msgid="3717639178595958667">"এপ্ খোলক"</string>
<string name="dismiss" msgid="6192859333764711227">"অগ্ৰাহ্য কৰক"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-az/strings.xml b/packages/VpnDialogs/res/values-az/strings.xml
index d878835..ca0066f 100644
--- a/packages/VpnDialogs/res/values-az/strings.xml
+++ b/packages/VpnDialogs/res/values-az/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Əlaqəni kəs"</string>
<string name="open_app" msgid="3717639178595958667">"Tətbiqi açın"</string>
<string name="dismiss" msgid="6192859333764711227">"İmtina edin"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml b/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
index a1075d2..9f0b486 100644
--- a/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
+++ b/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Prekini vezu"</string>
<string name="open_app" msgid="3717639178595958667">"Otvori aplikaciju"</string>
<string name="dismiss" msgid="6192859333764711227">"Odbaci"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-be/strings.xml b/packages/VpnDialogs/res/values-be/strings.xml
index fc3f878..3621798 100644
--- a/packages/VpnDialogs/res/values-be/strings.xml
+++ b/packages/VpnDialogs/res/values-be/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Адключыцца"</string>
<string name="open_app" msgid="3717639178595958667">"Адкрыць праграму"</string>
<string name="dismiss" msgid="6192859333764711227">"Адхіліць"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-bg/strings.xml b/packages/VpnDialogs/res/values-bg/strings.xml
index 6345f1d..df487ee 100644
--- a/packages/VpnDialogs/res/values-bg/strings.xml
+++ b/packages/VpnDialogs/res/values-bg/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Изключване"</string>
<string name="open_app" msgid="3717639178595958667">"Към приложението"</string>
<string name="dismiss" msgid="6192859333764711227">"Отхвърляне"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-bn/strings.xml b/packages/VpnDialogs/res/values-bn/strings.xml
index 352b786..52145d8 100644
--- a/packages/VpnDialogs/res/values-bn/strings.xml
+++ b/packages/VpnDialogs/res/values-bn/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"সংযোগ বিচ্ছিন্ন করুন"</string>
<string name="open_app" msgid="3717639178595958667">"অ্যাপটি খুলুন"</string>
<string name="dismiss" msgid="6192859333764711227">"খারিজ করুন"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-bs/strings.xml b/packages/VpnDialogs/res/values-bs/strings.xml
index fa5f4ea..0626b64 100644
--- a/packages/VpnDialogs/res/values-bs/strings.xml
+++ b/packages/VpnDialogs/res/values-bs/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Prekini vezu"</string>
<string name="open_app" msgid="3717639178595958667">"Otvori aplikaciju"</string>
<string name="dismiss" msgid="6192859333764711227">"Odbaci"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ca/strings.xml b/packages/VpnDialogs/res/values-ca/strings.xml
index cdb7547..b8410ef 100644
--- a/packages/VpnDialogs/res/values-ca/strings.xml
+++ b/packages/VpnDialogs/res/values-ca/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Desconnecta"</string>
<string name="open_app" msgid="3717639178595958667">"Obre l\'aplicació"</string>
<string name="dismiss" msgid="6192859333764711227">"Ignora"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-cs/strings.xml b/packages/VpnDialogs/res/values-cs/strings.xml
index c06f6ff..b1608f9 100644
--- a/packages/VpnDialogs/res/values-cs/strings.xml
+++ b/packages/VpnDialogs/res/values-cs/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Odpojit"</string>
<string name="open_app" msgid="3717639178595958667">"Do aplikace"</string>
<string name="dismiss" msgid="6192859333764711227">"Zavřít"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-da/strings.xml b/packages/VpnDialogs/res/values-da/strings.xml
index a4ddc19..2931cb2 100644
--- a/packages/VpnDialogs/res/values-da/strings.xml
+++ b/packages/VpnDialogs/res/values-da/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Fjern tilknytning"</string>
<string name="open_app" msgid="3717639178595958667">"Åbn app"</string>
<string name="dismiss" msgid="6192859333764711227">"Luk"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-de/strings.xml b/packages/VpnDialogs/res/values-de/strings.xml
index f38e395..033e550 100644
--- a/packages/VpnDialogs/res/values-de/strings.xml
+++ b/packages/VpnDialogs/res/values-de/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Verbindungsanfrage"</string>
- <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. Wenn VPN aktiv ist, wird oben im Display <br /> <br /> <img src=vpn_icon /> angezeigt."</string>
+ <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. <br /> <br /> Wenn das VPN aktiv ist, wird oben im Display <img src=vpn_icon /> angezeigt."</string>
<string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. <br /> <br /> <img src=vpn_icon /> wird auf dem Display angezeigt, wenn VPN aktiv ist."</string>
<string name="legacy_title" msgid="192936250066580964">"VPN ist verbunden"</string>
<string name="session" msgid="6470628549473641030">"Sitzung:"</string>
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Verbindung trennen"</string>
<string name="open_app" msgid="3717639178595958667">"App öffnen"</string>
<string name="dismiss" msgid="6192859333764711227">"Schließen"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-el/strings.xml b/packages/VpnDialogs/res/values-el/strings.xml
index e3eb460..2c527e8 100644
--- a/packages/VpnDialogs/res/values-el/strings.xml
+++ b/packages/VpnDialogs/res/values-el/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Αποσύνδεση"</string>
<string name="open_app" msgid="3717639178595958667">"Άνοιγμα εφαρμογής"</string>
<string name="dismiss" msgid="6192859333764711227">"Παράβλεψη"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-en-rAU/strings.xml b/packages/VpnDialogs/res/values-en-rAU/strings.xml
index cb8b79d..7855b24 100644
--- a/packages/VpnDialogs/res/values-en-rAU/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rAU/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
<string name="open_app" msgid="3717639178595958667">"Open app"</string>
<string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-en-rCA/strings.xml b/packages/VpnDialogs/res/values-en-rCA/strings.xml
index cb8b79d..eca5db0 100644
--- a/packages/VpnDialogs/res/values-en-rCA/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rCA/strings.xml
@@ -26,12 +26,14 @@
<string name="data_received" msgid="4062776929376067820">"Received:"</string>
<string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> bytes / <xliff:g id="NUMBER_1">%2$s</xliff:g> packets"</string>
<string name="always_on_disconnected_title" msgid="1906740176262776166">"Can\'t connect to always-on VPN"</string>
- <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect at the moment. Your phone will use a public network until it can reconnect to <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
- <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect at the moment. You won\'t have a connection until the VPN can reconnect."</string>
+ <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect right now. Your phone will use a public network until it can reconnect to <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
+ <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect right now. You won\'t have a connection until the VPN can reconnect."</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
<string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Change VPN settings"</string>
<string name="configure" msgid="4905518375574791375">"Configure"</string>
<string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
<string name="open_app" msgid="3717639178595958667">"Open app"</string>
<string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-en-rGB/strings.xml b/packages/VpnDialogs/res/values-en-rGB/strings.xml
index cb8b79d..7855b24 100644
--- a/packages/VpnDialogs/res/values-en-rGB/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rGB/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
<string name="open_app" msgid="3717639178595958667">"Open app"</string>
<string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-en-rIN/strings.xml b/packages/VpnDialogs/res/values-en-rIN/strings.xml
index cb8b79d..7855b24 100644
--- a/packages/VpnDialogs/res/values-en-rIN/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rIN/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
<string name="open_app" msgid="3717639178595958667">"Open app"</string>
<string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-en-rXC/strings.xml b/packages/VpnDialogs/res/values-en-rXC/strings.xml
index f5e2deb..06d1421 100644
--- a/packages/VpnDialogs/res/values-en-rXC/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rXC/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
<string name="open_app" msgid="3717639178595958667">"Open app"</string>
<string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-es-rUS/strings.xml b/packages/VpnDialogs/res/values-es-rUS/strings.xml
index 232b53a..b75e4bf 100644
--- a/packages/VpnDialogs/res/values-es-rUS/strings.xml
+++ b/packages/VpnDialogs/res/values-es-rUS/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
<string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
<string name="dismiss" msgid="6192859333764711227">"Descartar"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-es/strings.xml b/packages/VpnDialogs/res/values-es/strings.xml
index 4e21fd09..d73e2fd 100644
--- a/packages/VpnDialogs/res/values-es/strings.xml
+++ b/packages/VpnDialogs/res/values-es/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
<string name="open_app" msgid="3717639178595958667">"Abrir aplicación"</string>
<string name="dismiss" msgid="6192859333764711227">"Cerrar"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-et/strings.xml b/packages/VpnDialogs/res/values-et/strings.xml
index 140c183..0e335f2 100644
--- a/packages/VpnDialogs/res/values-et/strings.xml
+++ b/packages/VpnDialogs/res/values-et/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Katkesta ühendus"</string>
<string name="open_app" msgid="3717639178595958667">"Ava rakendus"</string>
<string name="dismiss" msgid="6192859333764711227">"Loobu"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-eu/strings.xml b/packages/VpnDialogs/res/values-eu/strings.xml
index a27a66a..e7d3e2b 100644
--- a/packages/VpnDialogs/res/values-eu/strings.xml
+++ b/packages/VpnDialogs/res/values-eu/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Deskonektatu"</string>
<string name="open_app" msgid="3717639178595958667">"Ireki aplikazioa"</string>
<string name="dismiss" msgid="6192859333764711227">"Baztertu"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-fa/strings.xml b/packages/VpnDialogs/res/values-fa/strings.xml
index 6fb5a00..07f8d18 100644
--- a/packages/VpnDialogs/res/values-fa/strings.xml
+++ b/packages/VpnDialogs/res/values-fa/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"قطع اتصال"</string>
<string name="open_app" msgid="3717639178595958667">"باز کردن برنامه"</string>
<string name="dismiss" msgid="6192859333764711227">"رد کردن"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-fi/strings.xml b/packages/VpnDialogs/res/values-fi/strings.xml
index 8abca06..7e5af3a 100644
--- a/packages/VpnDialogs/res/values-fi/strings.xml
+++ b/packages/VpnDialogs/res/values-fi/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Katkaise yhteys"</string>
<string name="open_app" msgid="3717639178595958667">"Avaa sovellus"</string>
<string name="dismiss" msgid="6192859333764711227">"Hylkää"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-fr-rCA/strings.xml b/packages/VpnDialogs/res/values-fr-rCA/strings.xml
index 876111c..2a1718b 100644
--- a/packages/VpnDialogs/res/values-fr-rCA/strings.xml
+++ b/packages/VpnDialogs/res/values-fr-rCA/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Déconnecter"</string>
<string name="open_app" msgid="3717639178595958667">"Ouvrir l\'application"</string>
<string name="dismiss" msgid="6192859333764711227">"Ignorer"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-fr/strings.xml b/packages/VpnDialogs/res/values-fr/strings.xml
index 27ebfb0..ba5f092 100644
--- a/packages/VpnDialogs/res/values-fr/strings.xml
+++ b/packages/VpnDialogs/res/values-fr/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Déconnecter"</string>
<string name="open_app" msgid="3717639178595958667">"Ouvrir l\'application"</string>
<string name="dismiss" msgid="6192859333764711227">"Ignorer"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-gl/strings.xml b/packages/VpnDialogs/res/values-gl/strings.xml
index 08ab9ae..b2e3034 100644
--- a/packages/VpnDialogs/res/values-gl/strings.xml
+++ b/packages/VpnDialogs/res/values-gl/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
<string name="open_app" msgid="3717639178595958667">"Abrir aplicación"</string>
<string name="dismiss" msgid="6192859333764711227">"Pechar"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-gu/strings.xml b/packages/VpnDialogs/res/values-gu/strings.xml
index 5ffdcb1..6e9bd32 100644
--- a/packages/VpnDialogs/res/values-gu/strings.xml
+++ b/packages/VpnDialogs/res/values-gu/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ડિસ્કનેક્ટ કરો"</string>
<string name="open_app" msgid="3717639178595958667">"ઍપ ખોલો"</string>
<string name="dismiss" msgid="6192859333764711227">"છોડી દો"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-hi/strings.xml b/packages/VpnDialogs/res/values-hi/strings.xml
index c9c65d5..3e65649 100644
--- a/packages/VpnDialogs/res/values-hi/strings.xml
+++ b/packages/VpnDialogs/res/values-hi/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"डिसकनेक्ट करें"</string>
<string name="open_app" msgid="3717639178595958667">"ऐप खोलें"</string>
<string name="dismiss" msgid="6192859333764711227">"खारिज करें"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-hr/strings.xml b/packages/VpnDialogs/res/values-hr/strings.xml
index 576d997..dddaa58 100644
--- a/packages/VpnDialogs/res/values-hr/strings.xml
+++ b/packages/VpnDialogs/res/values-hr/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Prekini vezu"</string>
<string name="open_app" msgid="3717639178595958667">"Otvori aplikaciju"</string>
<string name="dismiss" msgid="6192859333764711227">"Odbaci"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-hu/strings.xml b/packages/VpnDialogs/res/values-hu/strings.xml
index 69b999f..a719ef9 100644
--- a/packages/VpnDialogs/res/values-hu/strings.xml
+++ b/packages/VpnDialogs/res/values-hu/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Kapcsolat bontása"</string>
<string name="open_app" msgid="3717639178595958667">"Alkalmazás indítása"</string>
<string name="dismiss" msgid="6192859333764711227">"Bezárás"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-hy/strings.xml b/packages/VpnDialogs/res/values-hy/strings.xml
index d2a6d42..b67f79e8 100644
--- a/packages/VpnDialogs/res/values-hy/strings.xml
+++ b/packages/VpnDialogs/res/values-hy/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Անջատել"</string>
<string name="open_app" msgid="3717639178595958667">"Բացել հավելվածը"</string>
<string name="dismiss" msgid="6192859333764711227">"Փակել"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-in/strings.xml b/packages/VpnDialogs/res/values-in/strings.xml
index 88a588c..20da563 100644
--- a/packages/VpnDialogs/res/values-in/strings.xml
+++ b/packages/VpnDialogs/res/values-in/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Putuskan koneksi"</string>
<string name="open_app" msgid="3717639178595958667">"Buka aplikasi"</string>
<string name="dismiss" msgid="6192859333764711227">"Tutup"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-is/strings.xml b/packages/VpnDialogs/res/values-is/strings.xml
index a75371d..ab476b2 100644
--- a/packages/VpnDialogs/res/values-is/strings.xml
+++ b/packages/VpnDialogs/res/values-is/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Aftengja"</string>
<string name="open_app" msgid="3717639178595958667">"Opna forrit"</string>
<string name="dismiss" msgid="6192859333764711227">"Hunsa"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-it/strings.xml b/packages/VpnDialogs/res/values-it/strings.xml
index c443c51..19347bf 100644
--- a/packages/VpnDialogs/res/values-it/strings.xml
+++ b/packages/VpnDialogs/res/values-it/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Richiesta di connessione"</string>
- <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vuole impostare una connessione VPN che le consenta di monitorare il traffico di rete. Accetta soltanto se ritieni la fonte attendibile. Quando la connessione VPN è attiva, nella parte superiore dello schermo viene visualizzata l\'icona <br /> <br /> <img src=vpn_icon />."</string>
+ <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vuole impostare una connessione VPN per monitorare il traffico di rete. Accetta soltanto se ritieni la fonte attendibile. Quando la connessione VPN è attiva, in alto sullo schermo appare l\'icona <br /> <br /> <img src=vpn_icon />."</string>
<string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> vuole configurare una connessione VPN che le consenta di monitorare il traffico di rete. Accetta soltanto se ritieni la fonte attendibile. Quando la connessione VPN è attiva, sullo schermo viene visualizzata l\'icona <br /> <br /> <img src=vpn_icon />."</string>
<string name="legacy_title" msgid="192936250066580964">"VPN connessa"</string>
<string name="session" msgid="6470628549473641030">"Sessione:"</string>
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Disconnetti"</string>
<string name="open_app" msgid="3717639178595958667">"Apri app"</string>
<string name="dismiss" msgid="6192859333764711227">"Ignora"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-iw/strings.xml b/packages/VpnDialogs/res/values-iw/strings.xml
index 56d8105..3d4e0f0 100644
--- a/packages/VpnDialogs/res/values-iw/strings.xml
+++ b/packages/VpnDialogs/res/values-iw/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"נתק"</string>
<string name="open_app" msgid="3717639178595958667">"פתיחת האפליקציה"</string>
<string name="dismiss" msgid="6192859333764711227">"סגירה"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-ja/strings.xml b/packages/VpnDialogs/res/values-ja/strings.xml
index e03e9d3..658569f 100644
--- a/packages/VpnDialogs/res/values-ja/strings.xml
+++ b/packages/VpnDialogs/res/values-ja/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"切断"</string>
<string name="open_app" msgid="3717639178595958667">"アプリを開く"</string>
<string name="dismiss" msgid="6192859333764711227">"閉じる"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>…(<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g>(<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-ka/strings.xml b/packages/VpnDialogs/res/values-ka/strings.xml
index 9c4388e..d63b416 100644
--- a/packages/VpnDialogs/res/values-ka/strings.xml
+++ b/packages/VpnDialogs/res/values-ka/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"კავშირის გაწყვეტა"</string>
<string name="open_app" msgid="3717639178595958667">"გახსენით აპი"</string>
<string name="dismiss" msgid="6192859333764711227">"დახურვა"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-kk/strings.xml b/packages/VpnDialogs/res/values-kk/strings.xml
index 9a499d3..b109d91 100644
--- a/packages/VpnDialogs/res/values-kk/strings.xml
+++ b/packages/VpnDialogs/res/values-kk/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Ажырату"</string>
<string name="open_app" msgid="3717639178595958667">"Қолданбаны ашу"</string>
<string name="dismiss" msgid="6192859333764711227">"Жабу"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-km/strings.xml b/packages/VpnDialogs/res/values-km/strings.xml
index de18aba..7e4e9b6 100644
--- a/packages/VpnDialogs/res/values-km/strings.xml
+++ b/packages/VpnDialogs/res/values-km/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ផ្ដាច់"</string>
<string name="open_app" msgid="3717639178595958667">"បើកកម្មវិធី"</string>
<string name="dismiss" msgid="6192859333764711227">"ច្រានចោល"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-kn/strings.xml b/packages/VpnDialogs/res/values-kn/strings.xml
index 6308f18..864efd5 100644
--- a/packages/VpnDialogs/res/values-kn/strings.xml
+++ b/packages/VpnDialogs/res/values-kn/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
<string name="open_app" msgid="3717639178595958667">"ಅಪ್ಲಿಕೇಶನ್ ತೆರೆಯಿರಿ"</string>
<string name="dismiss" msgid="6192859333764711227">"ವಜಾಗೊಳಿಸಿ"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ko/strings.xml b/packages/VpnDialogs/res/values-ko/strings.xml
index 6e179bb..15aa323 100644
--- a/packages/VpnDialogs/res/values-ko/strings.xml
+++ b/packages/VpnDialogs/res/values-ko/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"연결 끊기"</string>
<string name="open_app" msgid="3717639178595958667">"앱 열기"</string>
<string name="dismiss" msgid="6192859333764711227">"닫기"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ky/strings.xml b/packages/VpnDialogs/res/values-ky/strings.xml
index 31f9e2d..0773984 100644
--- a/packages/VpnDialogs/res/values-ky/strings.xml
+++ b/packages/VpnDialogs/res/values-ky/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Ажыратуу"</string>
<string name="open_app" msgid="3717639178595958667">"Колдонмону ачуу"</string>
<string name="dismiss" msgid="6192859333764711227">"Четке кагуу"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-lo/strings.xml b/packages/VpnDialogs/res/values-lo/strings.xml
index cec69f0..747e1f4 100644
--- a/packages/VpnDialogs/res/values-lo/strings.xml
+++ b/packages/VpnDialogs/res/values-lo/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ຕັດການເຊື່ອມຕໍ່"</string>
<string name="open_app" msgid="3717639178595958667">"ເປີດແອັບ"</string>
<string name="dismiss" msgid="6192859333764711227">"ປິດໄວ້"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-lt/strings.xml b/packages/VpnDialogs/res/values-lt/strings.xml
index 97abd0d..1d9d570 100644
--- a/packages/VpnDialogs/res/values-lt/strings.xml
+++ b/packages/VpnDialogs/res/values-lt/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Atsijungti"</string>
<string name="open_app" msgid="3717639178595958667">"Atidaryti programą"</string>
<string name="dismiss" msgid="6192859333764711227">"Atsisakyti"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-lv/strings.xml b/packages/VpnDialogs/res/values-lv/strings.xml
index 6341fbd..e3a2db8 100644
--- a/packages/VpnDialogs/res/values-lv/strings.xml
+++ b/packages/VpnDialogs/res/values-lv/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Pārtraukt savienojumu"</string>
<string name="open_app" msgid="3717639178595958667">"Atvērt lietotni"</string>
<string name="dismiss" msgid="6192859333764711227">"Nerādīt"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-mk/strings.xml b/packages/VpnDialogs/res/values-mk/strings.xml
index 689d028..867e6d1 100644
--- a/packages/VpnDialogs/res/values-mk/strings.xml
+++ b/packages/VpnDialogs/res/values-mk/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Прекини врска"</string>
<string name="open_app" msgid="3717639178595958667">"Отвори ја апликацијата"</string>
<string name="dismiss" msgid="6192859333764711227">"Отфрли"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ml/strings.xml b/packages/VpnDialogs/res/values-ml/strings.xml
index 8284a78..2c5b048 100644
--- a/packages/VpnDialogs/res/values-ml/strings.xml
+++ b/packages/VpnDialogs/res/values-ml/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"വിച്ഛേദിക്കുക"</string>
<string name="open_app" msgid="3717639178595958667">"ആപ്പ് തുറക്കുക"</string>
<string name="dismiss" msgid="6192859333764711227">"നിരസിക്കുക"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-mn/strings.xml b/packages/VpnDialogs/res/values-mn/strings.xml
index 1dd4c15..a0d8418 100644
--- a/packages/VpnDialogs/res/values-mn/strings.xml
+++ b/packages/VpnDialogs/res/values-mn/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Салгах"</string>
<string name="open_app" msgid="3717639178595958667">"Апп нээх"</string>
<string name="dismiss" msgid="6192859333764711227">"Хаах"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-mr/strings.xml b/packages/VpnDialogs/res/values-mr/strings.xml
index 22fb502..6aeb9e7 100644
--- a/packages/VpnDialogs/res/values-mr/strings.xml
+++ b/packages/VpnDialogs/res/values-mr/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"डिस्कनेक्ट करा"</string>
<string name="open_app" msgid="3717639178595958667">"अॅप उघडा"</string>
<string name="dismiss" msgid="6192859333764711227">"डिसमिस करा"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ms/strings.xml b/packages/VpnDialogs/res/values-ms/strings.xml
index c9961d2..fe2f433 100644
--- a/packages/VpnDialogs/res/values-ms/strings.xml
+++ b/packages/VpnDialogs/res/values-ms/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Putuskan sambungan"</string>
<string name="open_app" msgid="3717639178595958667">"Buka apl"</string>
<string name="dismiss" msgid="6192859333764711227">"Ketepikan"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-my/strings.xml b/packages/VpnDialogs/res/values-my/strings.xml
index 36348c8..02bb68d 100644
--- a/packages/VpnDialogs/res/values-my/strings.xml
+++ b/packages/VpnDialogs/res/values-my/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
<string name="open_app" msgid="3717639178595958667">"အက်ပ်ကို ဖွင့်ရန်"</string>
<string name="dismiss" msgid="6192859333764711227">"ပယ်ရန်"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-nb/strings.xml b/packages/VpnDialogs/res/values-nb/strings.xml
index 14c84d7..9904745 100644
--- a/packages/VpnDialogs/res/values-nb/strings.xml
+++ b/packages/VpnDialogs/res/values-nb/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Koble fra"</string>
<string name="open_app" msgid="3717639178595958667">"Åpne appen"</string>
<string name="dismiss" msgid="6192859333764711227">"Lukk"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ne/strings.xml b/packages/VpnDialogs/res/values-ne/strings.xml
index 2a5648d..1453dfb 100644
--- a/packages/VpnDialogs/res/values-ne/strings.xml
+++ b/packages/VpnDialogs/res/values-ne/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"डिस्कनेक्ट गर्नुहोस्"</string>
<string name="open_app" msgid="3717639178595958667">"एप खोल्नुहोस्"</string>
<string name="dismiss" msgid="6192859333764711227">"खारेज गर्नुहोस्"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-nl/strings.xml b/packages/VpnDialogs/res/values-nl/strings.xml
index 76f56af..4223cf4 100644
--- a/packages/VpnDialogs/res/values-nl/strings.xml
+++ b/packages/VpnDialogs/res/values-nl/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Verbinding verbreken"</string>
<string name="open_app" msgid="3717639178595958667">"App openen"</string>
<string name="dismiss" msgid="6192859333764711227">"Sluiten"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-or/strings.xml b/packages/VpnDialogs/res/values-or/strings.xml
index 4c5c259..2714af3 100644
--- a/packages/VpnDialogs/res/values-or/strings.xml
+++ b/packages/VpnDialogs/res/values-or/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
<string name="open_app" msgid="3717639178595958667">"ଆପ୍ ଖୋଲନ୍ତୁ"</string>
<string name="dismiss" msgid="6192859333764711227">"ଖାରଜ କରନ୍ତୁ"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-pa/strings.xml b/packages/VpnDialogs/res/values-pa/strings.xml
index d2eba0f..969d5e2 100644
--- a/packages/VpnDialogs/res/values-pa/strings.xml
+++ b/packages/VpnDialogs/res/values-pa/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
<string name="open_app" msgid="3717639178595958667">"ਐਪ ਖੋਲ੍ਹੋ"</string>
<string name="dismiss" msgid="6192859333764711227">"ਖਾਰਜ ਕਰੋ"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-pl/strings.xml b/packages/VpnDialogs/res/values-pl/strings.xml
index 82161d3..199988f 100644
--- a/packages/VpnDialogs/res/values-pl/strings.xml
+++ b/packages/VpnDialogs/res/values-pl/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Rozłącz"</string>
<string name="open_app" msgid="3717639178595958667">"Otwórz aplikację"</string>
<string name="dismiss" msgid="6192859333764711227">"Zamknij"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-pt-rBR/strings.xml b/packages/VpnDialogs/res/values-pt-rBR/strings.xml
index 0d6dd0b..8718d76 100644
--- a/packages/VpnDialogs/res/values-pt-rBR/strings.xml
+++ b/packages/VpnDialogs/res/values-pt-rBR/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
<string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
<string name="dismiss" msgid="6192859333764711227">"Dispensar"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-pt-rPT/strings.xml b/packages/VpnDialogs/res/values-pt-rPT/strings.xml
index a310104..95f7c1a 100644
--- a/packages/VpnDialogs/res/values-pt-rPT/strings.xml
+++ b/packages/VpnDialogs/res/values-pt-rPT/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"Desligar"</string>
<string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
<string name="dismiss" msgid="6192859333764711227">"Ignorar"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-pt/strings.xml b/packages/VpnDialogs/res/values-pt/strings.xml
index 0d6dd0b..8718d76 100644
--- a/packages/VpnDialogs/res/values-pt/strings.xml
+++ b/packages/VpnDialogs/res/values-pt/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
<string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
<string name="dismiss" msgid="6192859333764711227">"Dispensar"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ro/strings.xml b/packages/VpnDialogs/res/values-ro/strings.xml
index f86a5d6..1b18afa 100644
--- a/packages/VpnDialogs/res/values-ro/strings.xml
+++ b/packages/VpnDialogs/res/values-ro/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Deconectează"</string>
<string name="open_app" msgid="3717639178595958667">"Deschide aplicația"</string>
<string name="dismiss" msgid="6192859333764711227">"Închide"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ru/strings.xml b/packages/VpnDialogs/res/values-ru/strings.xml
index ce099562..06d7e02 100644
--- a/packages/VpnDialogs/res/values-ru/strings.xml
+++ b/packages/VpnDialogs/res/values-ru/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Разъединить"</string>
<string name="open_app" msgid="3717639178595958667">"Открыть приложение"</string>
<string name="dismiss" msgid="6192859333764711227">"Закрыть"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-si/strings.xml b/packages/VpnDialogs/res/values-si/strings.xml
index a836bae..23c8c22 100644
--- a/packages/VpnDialogs/res/values-si/strings.xml
+++ b/packages/VpnDialogs/res/values-si/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"විසන්ධි කරන්න"</string>
<string name="open_app" msgid="3717639178595958667">"යෙදුම විවෘත කරන්න"</string>
<string name="dismiss" msgid="6192859333764711227">"ඉවතලන්න"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-sk/strings.xml b/packages/VpnDialogs/res/values-sk/strings.xml
index 766c139..22f390c 100644
--- a/packages/VpnDialogs/res/values-sk/strings.xml
+++ b/packages/VpnDialogs/res/values-sk/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Odpojiť"</string>
<string name="open_app" msgid="3717639178595958667">"Otvoriť aplikáciu"</string>
<string name="dismiss" msgid="6192859333764711227">"Zavrieť"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-sl/strings.xml b/packages/VpnDialogs/res/values-sl/strings.xml
index 361a5fa..2f35e34 100644
--- a/packages/VpnDialogs/res/values-sl/strings.xml
+++ b/packages/VpnDialogs/res/values-sl/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Prekini povezavo"</string>
<string name="open_app" msgid="3717639178595958667">"Odpri aplikacijo"</string>
<string name="dismiss" msgid="6192859333764711227">"Opusti"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-sq/strings.xml b/packages/VpnDialogs/res/values-sq/strings.xml
index eb73baa..174b278 100644
--- a/packages/VpnDialogs/res/values-sq/strings.xml
+++ b/packages/VpnDialogs/res/values-sq/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Shkëputu"</string>
<string name="open_app" msgid="3717639178595958667">"Hap aplikacionin"</string>
<string name="dismiss" msgid="6192859333764711227">"Hiq"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-sr/strings.xml b/packages/VpnDialogs/res/values-sr/strings.xml
index 01bd4df..019a5b4 100644
--- a/packages/VpnDialogs/res/values-sr/strings.xml
+++ b/packages/VpnDialogs/res/values-sr/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Прекини везу"</string>
<string name="open_app" msgid="3717639178595958667">"Отвори апликацију"</string>
<string name="dismiss" msgid="6192859333764711227">"Одбаци"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-sv/strings.xml b/packages/VpnDialogs/res/values-sv/strings.xml
index 60ed752..5e0aec3 100644
--- a/packages/VpnDialogs/res/values-sv/strings.xml
+++ b/packages/VpnDialogs/res/values-sv/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Koppla från"</string>
<string name="open_app" msgid="3717639178595958667">"Öppna appen"</string>
<string name="dismiss" msgid="6192859333764711227">"Ignorera"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-sw/strings.xml b/packages/VpnDialogs/res/values-sw/strings.xml
index c4f4662..1dfbe7a 100644
--- a/packages/VpnDialogs/res/values-sw/strings.xml
+++ b/packages/VpnDialogs/res/values-sw/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Tenganisha"</string>
<string name="open_app" msgid="3717639178595958667">"Fungua programu"</string>
<string name="dismiss" msgid="6192859333764711227">"Ondoa"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ta/strings.xml b/packages/VpnDialogs/res/values-ta/strings.xml
index 1385bdc..87f64de 100644
--- a/packages/VpnDialogs/res/values-ta/strings.xml
+++ b/packages/VpnDialogs/res/values-ta/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"தொடர்பைத் துண்டி"</string>
<string name="open_app" msgid="3717639178595958667">"பயன்பாட்டைத் திற"</string>
<string name="dismiss" msgid="6192859333764711227">"நிராகரி"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-te/strings.xml b/packages/VpnDialogs/res/values-te/strings.xml
index 7884336..fcf54bc 100644
--- a/packages/VpnDialogs/res/values-te/strings.xml
+++ b/packages/VpnDialogs/res/values-te/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"డిస్కనెక్ట్ చేయి"</string>
<string name="open_app" msgid="3717639178595958667">"యాప్ని తెరవండి"</string>
<string name="dismiss" msgid="6192859333764711227">"తీసివేయండి"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-th/strings.xml b/packages/VpnDialogs/res/values-th/strings.xml
index 2e174cd..3e15d4b 100644
--- a/packages/VpnDialogs/res/values-th/strings.xml
+++ b/packages/VpnDialogs/res/values-th/strings.xml
@@ -34,4 +34,6 @@
<string name="disconnect" msgid="971412338304200056">"ยกเลิกการเชื่อมต่อ"</string>
<string name="open_app" msgid="3717639178595958667">"เปิดแอป"</string>
<string name="dismiss" msgid="6192859333764711227">"ปิด"</string>
+ <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+ <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-tl/strings.xml b/packages/VpnDialogs/res/values-tl/strings.xml
index ea69fba..bb099e7 100644
--- a/packages/VpnDialogs/res/values-tl/strings.xml
+++ b/packages/VpnDialogs/res/values-tl/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Idiskonekta"</string>
<string name="open_app" msgid="3717639178595958667">"Buksan ang app"</string>
<string name="dismiss" msgid="6192859333764711227">"I-dismiss"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-tr/strings.xml b/packages/VpnDialogs/res/values-tr/strings.xml
index 7ffa4bc..8204234 100644
--- a/packages/VpnDialogs/res/values-tr/strings.xml
+++ b/packages/VpnDialogs/res/values-tr/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Bağlantıyı kes"</string>
<string name="open_app" msgid="3717639178595958667">"Uygulamayı aç"</string>
<string name="dismiss" msgid="6192859333764711227">"Kapat"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-uk/strings.xml b/packages/VpnDialogs/res/values-uk/strings.xml
index 6411d7c..3890096 100644
--- a/packages/VpnDialogs/res/values-uk/strings.xml
+++ b/packages/VpnDialogs/res/values-uk/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Від’єднати"</string>
<string name="open_app" msgid="3717639178595958667">"Відкрити додаток"</string>
<string name="dismiss" msgid="6192859333764711227">"Закрити"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-ur/strings.xml b/packages/VpnDialogs/res/values-ur/strings.xml
index 3a23e94..7fa828a 100644
--- a/packages/VpnDialogs/res/values-ur/strings.xml
+++ b/packages/VpnDialogs/res/values-ur/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"منقطع کریں"</string>
<string name="open_app" msgid="3717639178595958667">"ایپ کھولیں"</string>
<string name="dismiss" msgid="6192859333764711227">"برخاست کریں"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-uz/strings.xml b/packages/VpnDialogs/res/values-uz/strings.xml
index a3256e7..80dcf94 100644
--- a/packages/VpnDialogs/res/values-uz/strings.xml
+++ b/packages/VpnDialogs/res/values-uz/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Aloqani uzish"</string>
<string name="open_app" msgid="3717639178595958667">"Ilovani ochish"</string>
<string name="dismiss" msgid="6192859333764711227">"Yopish"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-vi/strings.xml b/packages/VpnDialogs/res/values-vi/strings.xml
index 184d08d..7d8cc86 100644
--- a/packages/VpnDialogs/res/values-vi/strings.xml
+++ b/packages/VpnDialogs/res/values-vi/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Ngắt kết nối"</string>
<string name="open_app" msgid="3717639178595958667">"Mở ứng dụng"</string>
<string name="dismiss" msgid="6192859333764711227">"Loại bỏ"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-zh-rCN/strings.xml b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
index a7262be..1d8adbb 100644
--- a/packages/VpnDialogs/res/values-zh-rCN/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"断开连接"</string>
<string name="open_app" msgid="3717639178595958667">"打开应用"</string>
<string name="dismiss" msgid="6192859333764711227">"关闭"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-zh-rHK/strings.xml b/packages/VpnDialogs/res/values-zh-rHK/strings.xml
index e4e6432..a0d6ee0 100644
--- a/packages/VpnDialogs/res/values-zh-rHK/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rHK/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"中斷連線"</string>
<string name="open_app" msgid="3717639178595958667">"開啟應用程式"</string>
<string name="dismiss" msgid="6192859333764711227">"關閉"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-zh-rTW/strings.xml b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
index f54ca4a..948bc59 100644
--- a/packages/VpnDialogs/res/values-zh-rTW/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"中斷連線"</string>
<string name="open_app" msgid="3717639178595958667">"開啟應用程式"</string>
<string name="dismiss" msgid="6192859333764711227">"關閉"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values-zu/strings.xml b/packages/VpnDialogs/res/values-zu/strings.xml
index c224b13..875873f 100644
--- a/packages/VpnDialogs/res/values-zu/strings.xml
+++ b/packages/VpnDialogs/res/values-zu/strings.xml
@@ -34,4 +34,8 @@
<string name="disconnect" msgid="971412338304200056">"Ayixhumekile kwi-inthanethi"</string>
<string name="open_app" msgid="3717639178595958667">"Vula uhlelo lokusebenza"</string>
<string name="dismiss" msgid="6192859333764711227">"Cashisa"</string>
+ <!-- no translation found for sanitized_vpn_label_with_ellipsis (7014327474633422235) -->
+ <skip />
+ <!-- no translation found for sanitized_vpn_label (1877415015009794766) -->
+ <skip />
</resources>
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index f971a09..28e7272 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -100,4 +100,33 @@
without any consequences. [CHAR LIMIT=20] -->
<string name="dismiss">Dismiss</string>
+ <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+ into displaying what they want. The system will attempt to sanitize the label, and if the
+ label is deemed dangerous, then this string is used instead. The first argument is the
+ first 30 characters of the label, and the second argument is the package name of the app.
+ Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+ "My VPN app wants to set up a VPN connection...". If the label is very long, then, this
+ will be used to show "VerylongVPNlabel… (com.my.vpn.app) wants to set up a VPN
+ connection...". For this case, the code will refer to sanitized_vpn_label_with_ellipsis.
+ -->
+ <string name="sanitized_vpn_label_with_ellipsis">
+ <xliff:g id="sanitized_vpn_label_with_ellipsis" example="My VPN app">%1$s</xliff:g>… (
+ <xliff:g id="sanitized_vpn_label_with_ellipsis" example="com.my.vpn.app">%2$s</xliff:g>)
+ </string>
+
+ <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+ into displaying what they want. The system will attempt to sanitize the label, and if the
+ label is deemed dangerous, then this string is used instead. The first argument is the
+ label, and the second argument is the package name of the app.
+ Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+ "My VPN app wants to set up a VPN connection...". If the VPN label contains HTML tag but
+ the length is not very long, the dialog will show "VpnLabelWith<br>HtmlTag
+ (com.my.vpn.app) wants to set up a VPN connection...". For this case, the code will refer
+ to sanitized_vpn_label.
+ -->
+ <string name="sanitized_vpn_label">
+ <xliff:g id="sanitized_vpn_label" example="My VPN app">%1$s</xliff:g> (
+ <xliff:g id="sanitized_vpn_label" example="com.my.vpn.app">%2$s</xliff:g>)
+ </string>
+
</resources>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index fb23678..a98d6d8 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -33,6 +33,7 @@
import android.widget.Button;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AlertActivity;
import com.android.internal.net.VpnConfig;
@@ -40,12 +41,19 @@
implements DialogInterface.OnClickListener, ImageGetter {
private static final String TAG = "VpnConfirm";
+ // Usually the label represents the app name, 150 code points might be enough to display the app
+ // name, and 150 code points won't cover the warning message from VpnDialog.
+ @VisibleForTesting
+ static final int MAX_VPN_LABEL_LENGTH = 150;
+
@VpnManager.VpnType private final int mVpnType;
private String mPackage;
private VpnManager mVm;
+ private View mView;
+
public ConfirmDialog() {
this(VpnManager.TYPE_VPN_SERVICE);
}
@@ -54,6 +62,43 @@
mVpnType = vpnType;
}
+ /**
+ * This function will use the string resource to combine the VPN label and the package name.
+ *
+ * If the VPN label violates the length restriction, the first 30 code points of VPN label and
+ * the package name will be returned. Or return the VPN label and the package name directly if
+ * the VPN label doesn't violate the length restriction.
+ *
+ * The result will be something like,
+ * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app)
+ * if the VPN label violates the length restriction.
+ * or
+ * - VpnLabelWith<br>HtmlTag (com.vpn.app)
+ * if the VPN label doesn't violate the length restriction.
+ *
+ */
+ private String getSimplifiedLabel(String vpnLabel, String packageName) {
+ if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) {
+ return getString(R.string.sanitized_vpn_label_with_ellipsis,
+ vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)),
+ packageName);
+ }
+
+ return getString(R.string.sanitized_vpn_label, vpnLabel, packageName);
+ }
+
+ @VisibleForTesting
+ protected String getSanitizedVpnLabel(String vpnLabel, String packageName) {
+ final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel);
+ final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0,
+ sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH;
+ if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) {
+ return getSimplifiedLabel(sanitizedVpnLabel, packageName);
+ }
+
+ return sanitizedVpnLabel;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -75,15 +120,16 @@
finish();
return;
}
- View view = View.inflate(this, R.layout.confirm, null);
- ((TextView) view.findViewById(R.id.warning)).setText(
- Html.fromHtml(getString(R.string.warning, getVpnLabel()),
- this, null /* tagHandler */));
+ mView = View.inflate(this, R.layout.confirm, null);
+ ((TextView) mView.findViewById(R.id.warning)).setText(
+ Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel(
+ getVpnLabel().toString(), mPackage)),
+ this /* imageGetter */, null /* tagHandler */));
mAlertParams.mTitle = getText(R.string.prompt);
mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
mAlertParams.mPositiveButtonListener = this;
mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
- mAlertParams.mView = view;
+ mAlertParams.mView = mView;
setupAlert();
getWindow().setCloseOnTouchOutside(false);
@@ -92,6 +138,11 @@
button.setFilterTouchesWhenObscured(true);
}
+ @VisibleForTesting
+ public CharSequence getWarningText() {
+ return ((TextView) mView.findViewById(R.id.warning)).getText();
+ }
+
private CharSequence getVpnLabel() {
try {
return VpnConfig.getVpnLabel(this, mPackage);
diff --git a/packages/VpnDialogs/tests/Android.bp b/packages/VpnDialogs/tests/Android.bp
new file mode 100644
index 0000000..68639bd
--- /dev/null
+++ b/packages/VpnDialogs/tests/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "VpnDialogsTests",
+ // Use platform certificate because the test will invoke a hidden API.
+ // (e.g. VpnManager#prepareVpn()).
+ certificate: "platform",
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-minus-junit4",
+ "VpnDialogsLib",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/packages/VpnDialogs/tests/AndroidManifest.xml b/packages/VpnDialogs/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f26c1fe
--- /dev/null
+++ b/packages/VpnDialogs/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.vpndialogs.tests">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.vpndialogs.VpnDialogTest$InstrumentedConfirmDialog"/>
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.vpndialogs.tests"
+ android:label="Vpn dialog tests">
+ </instrumentation>
+</manifest>
diff --git a/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
new file mode 100644
index 0000000..7cfa466
--- /dev/null
+++ b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.vpndialogs;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.VpnManager;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class VpnDialogTest {
+ private ActivityScenario<ConfirmDialog> mActivityScenario;
+
+ @SuppressWarnings("StaticMockMember")
+ @Mock
+ private static PackageManager sPm;
+
+ @SuppressWarnings("StaticMockMember")
+ @Mock
+ private static VpnManager sVm;
+
+ @Mock
+ private ApplicationInfo mAi;
+
+ private static final String VPN_APP_NAME = "VpnApp";
+ private static final String VPN_APP_PACKAGE_NAME = "com.android.vpndialogs.VpnDialogTest";
+ private static final String VPN_LABEL_CONTAINS_HTML_TAG =
+ "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a>";
+ private static final String VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION =
+ "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a></b>"
+ + " Wants to connect the network. <br></br><br></br><br></br><br></br><br></br>"
+ + " <br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br> Deny it?";
+ private static final String VPN_LABEL_VIOLATES_LENGTH_RESTRICTION = "This is a VPN label"
+ + " which violates the length restriction. The length restriction here are 150 code"
+ + " points. So the VPN label should be sanitized, and shows the package name to the"
+ + " user.";
+
+ public static class InstrumentedConfirmDialog extends ConfirmDialog {
+ @Override
+ public PackageManager getPackageManager() {
+ return sPm;
+ }
+
+ @Override
+ public @Nullable Object getSystemService(@ServiceName @NonNull String name) {
+ switch (name) {
+ case Context.VPN_MANAGEMENT_SERVICE:
+ return sVm;
+ default:
+ return super.getSystemService(name);
+ }
+ }
+
+ @Override
+ public String getCallingPackage() {
+ return VPN_APP_PACKAGE_NAME;
+ }
+ }
+
+ private void launchActivity() {
+ final Context context = getInstrumentation().getContext();
+ mActivityScenario = ActivityScenario.launch(
+ new Intent(context, InstrumentedConfirmDialog.class));
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withNormalCase() throws Exception {
+ // Test the normal case that the VPN label showed in the VpnDialog is the app name.
+ doReturn(VPN_APP_NAME).when(mAi).loadLabel(sPm);
+ launchActivity();
+ mActivityScenario.onActivity(activity -> {
+ assertTrue(activity.getWarningText().toString().contains(VPN_APP_NAME));
+ });
+ }
+
+ private void verifySanitizedVpnLabel(String originalLabel) {
+ doReturn(originalLabel).when(mAi).loadLabel(sPm);
+ launchActivity();
+ mActivityScenario.onActivity(activity -> {
+ // The VPN label was sanitized because violating length restriction or having a html
+ // tag, so the warning message will contain the package name.
+ assertTrue(activity.getWarningText().toString().contains(activity.getCallingPackage()));
+ // Also, the length of sanitized VPN label shouldn't longer than MAX_VPN_LABEL_LENGTH
+ // and it shouldn't contain html tag.
+ final String sanitizedVpnLabel =
+ activity.getSanitizedVpnLabel(originalLabel, VPN_APP_PACKAGE_NAME);
+ assertTrue(sanitizedVpnLabel.codePointCount(0, sanitizedVpnLabel.length())
+ < ConfirmDialog.MAX_VPN_LABEL_LENGTH);
+ assertFalse(sanitizedVpnLabel.contains("<b>"));
+ });
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withHtmlTag() throws Exception {
+ // Test the case that the VPN label was sanitized because there is a html tag.
+ verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG);
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withHtmlTagAndViolateLengthRestriction() throws Exception {
+ // Test the case that the VPN label was sanitized because there is a html tag.
+ verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION);
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withLengthRestriction() throws Exception {
+ // Test the case that the VPN label was sanitized because hitting the length restriction.
+ verifySanitizedVpnLabel(VPN_LABEL_VIOLATES_LENGTH_RESTRICTION);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(false).when(sVm).prepareVpn(anyString(), anyString(), anyInt());
+ doReturn(null).when(sPm).queryIntentServices(any(), anyInt());
+ doReturn(mAi).when(sPm).getApplicationInfo(anyString(), anyInt());
+ }
+}
diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
index 9db960f..e7ec332 100644
--- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch hole cutout"</string>
+ <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch Hole cutout"</string>
</resources>
diff --git a/packages/overlays/NavigationBarMode3ButtonOverlay/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarMode3ButtonOverlay/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..8e466e0
--- /dev/null
+++ b/packages/overlays/NavigationBarMode3ButtonOverlay/res/values-sw600dp/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+ <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+ (which normally prevents seamless rotation). Allow seamless rotation because the bar
+ is relatively small in large screen and its appearance is similar to gestural mode
+ even if it jumps to another side for display orientation change. -->
+ <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+</resources>
+
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 90690a2..2ad2119 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -830,6 +830,15 @@
return null;
}
+
+ @Override
+ public boolean isCaptureProcessProgressAvailable() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ return mAdvancedExtender.isCaptureProcessProgressAvailable();
+ }
+
+ return false;
+ }
}
private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -923,6 +932,15 @@
Log.e(TAG, "Failed to notify capture complete due to remote exception!");
}
}
+
+ @Override
+ public void onCaptureProcessProgressed(int progress) {
+ try {
+ mCaptureCallback.onCaptureProcessProgressed(progress);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote client doesn't respond to capture progress callbacks!");
+ }
+ }
}
private class RequestCallbackStub extends IRequestCallback.Stub {
@@ -1455,6 +1473,15 @@
}
@Override
+ public boolean isCaptureProcessProgressAvailable() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ return mImageExtender.isCaptureProcessProgressAvailable();
+ }
+
+ return false;
+ }
+
+ @Override
public CaptureStageImpl onEnableSession() {
return initializeParcelable(mImageExtender.onEnableSession(), mCameraId);
}
@@ -1611,6 +1638,15 @@
}
@Override
+ public void onCaptureProcessProgressed(int progress) {
+ try {
+ mProcessResult.onCaptureProcessProgressed(progress);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote client doesn't respond to capture progress callbacks!");
+ }
+ }
+
+ @Override
public void onCaptureCompleted(long shutterTimestamp,
List<Pair<CaptureResult.Key, Object>> result) {
if (result == null) {
diff --git a/services/Android.bp b/services/Android.bp
index 76a1484..f6570e9 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -60,9 +60,17 @@
ignore_warnings: false,
proguard_flags_files: ["proguard.flags"],
},
- // Note: Optimizations are disabled by default if unspecified in
- // the java_library rule.
- conditions_default: {},
+ conditions_default: {
+ optimize: {
+ enabled: true,
+ optimize: false,
+ shrink: true,
+ ignore_warnings: false,
+ // Note that this proguard config is very conservative, only shrinking the
+ // permission subpackage to prune unused jarjar'ed Kotlin dependencies.
+ proguard_flags_files: ["proguard_permission.flags"],
+ },
+ },
},
},
}
@@ -97,6 +105,7 @@
":services.midi-sources",
":services.musicsearch-sources",
":services.net-sources",
+ ":services.permission-sources",
":services.print-sources",
":services.profcollect-sources",
":services.restrictions-sources",
@@ -131,6 +140,7 @@
app_image: true,
profile: "art-profile",
},
+ exclude_kotlinc_generated_files: true,
srcs: [":services-main-sources"],
@@ -152,6 +162,7 @@
"services.musicsearch",
"services.net",
"services.people",
+ "services.permission",
"services.print",
"services.profcollect",
"services.restrictions",
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f35de17..786d407 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
@@ -75,6 +76,7 @@
import android.view.Display;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInfo;
import android.view.accessibility.AccessibilityCache;
@@ -83,6 +85,7 @@
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import com.android.internal.annotations.GuardedBy;
@@ -211,6 +214,11 @@
/** The timestamp of requesting to take screenshot in milliseconds */
private long mRequestTakeScreenshotTimestampMs;
+ /**
+ * The timestamps of requesting to take a window screenshot in milliseconds,
+ * mapping from accessibility window id -> timestamp.
+ */
+ private SparseArray<Long> mRequestTakeScreenshotOfWindowTimestampMs = new SparseArray<>();
public interface SystemSupport {
/**
@@ -282,6 +290,8 @@
void requestImeLocked(AbstractAccessibilityServiceConnection connection);
void unbindImeLocked(AbstractAccessibilityServiceConnection connection);
+
+ void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc);
}
public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
@@ -1252,6 +1262,51 @@
}
@Override
+ public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+ final long currentTimestamp = SystemClock.uptimeMillis();
+ if ((currentTimestamp
+ - mRequestTakeScreenshotOfWindowTimestampMs.get(accessibilityWindowId, 0L))
+ <= ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT, interactionId);
+ return;
+ }
+ mRequestTakeScreenshotOfWindowTimestampMs.put(accessibilityWindowId, currentTimestamp);
+
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+ return;
+ }
+ if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+ interactionId);
+ return;
+ }
+ }
+ if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+ interactionId);
+ return;
+ }
+
+ RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked(
+ mSystemSupport.getCurrentUserIdLocked(),
+ resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+ if (connection == null) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId);
+ return;
+ }
+ connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback);
+ }
+
+ @Override
public void takeScreenshot(int displayId, RemoteCallback callback) {
if (svcConnTracingEnabled()) {
logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback);
@@ -2434,4 +2489,9 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
-}
\ No newline at end of file
+
+ @Override
+ public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
+ mSystemSupport.attachAccessibilityOverlayToDisplay(displayId, sc);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e3ae03c..87d1668 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -109,6 +109,7 @@
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
@@ -291,12 +292,12 @@
private Point mTempPoint = new Point();
private boolean mIsAccessibilityButtonShown;
-
private boolean mInputBound;
IRemoteAccessibilityInputConnection mRemoteInputConnection;
EditorInfo mEditorInfo;
boolean mRestarting;
boolean mInputSessionRequested;
+ private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
private AccessibilityUserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
@@ -3967,6 +3968,8 @@
synchronized (mLock) {
mDisplaysList.add(display);
+ mA11yOverlayLayers.put(
+ displayId, mWindowManagerService.getA11yOverlayLayer(displayId));
if (mInputFilter != null) {
mInputFilter.onDisplayAdded(display);
}
@@ -3990,6 +3993,7 @@
if (!removeDisplayFromList(displayId)) {
return;
}
+ mA11yOverlayLayers.remove(displayId);
if (mInputFilter != null) {
mInputFilter.onDisplayRemoved(displayId);
}
@@ -4691,4 +4695,30 @@
return true;
}
}
+
+ @Override
+ public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
+ mMainHandler.sendMessage(
+ obtainMessage(
+ AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
+ this,
+ displayId,
+ sc));
+ }
+
+ void attachAccessibilityOverlayToDisplayInternal(int displayId, SurfaceControl sc) {
+ if (!mA11yOverlayLayers.contains(displayId)) {
+ mA11yOverlayLayers.put(displayId, mWindowManagerService.getA11yOverlayLayer(displayId));
+ }
+ SurfaceControl parent = mA11yOverlayLayers.get(displayId);
+ if (parent == null) {
+ Slog.e(LOG_TAG, "Unable to get accessibility overlay SurfaceControl.");
+ mA11yOverlayLayers.remove(displayId);
+ return;
+ }
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ transaction.reparent(sc, parent);
+ transaction.apply();
+ transaction.close();
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 6958b66..c08b6ab 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -167,6 +167,12 @@
mServiceCallback.setPerformAccessibilityActionResult(succeeded, interactionId);
}
+ @Override
+ public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
+ throws RemoteException {
+ mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
+ }
+
private void replaceInfoActionsAndCallService() {
final AccessibilityNodeInfo nodeToReturn;
boolean doCallback = false;
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index 3ea1bcb..7d8bb51 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -114,7 +114,7 @@
info.minWidth = parser.getAttributeInt(null, ATTR_MIN_WIDTH, 0);
info.minHeight = parser.getAttributeInt(null, ATTR_MIN_HEIGHT, 0);
info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_WIDTH, 0);
- info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
+ info.minResizeHeight = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
info.maxResizeWidth = parser.getAttributeInt(null, ATTR_MAX_RESIZE_WIDTH, 0);
info.maxResizeHeight = parser.getAttributeInt(null, ATTR_MAX_RESIZE_HEIGHT, 0);
info.targetCellWidth = parser.getAttributeInt(null, ATTR_TARGET_CELL_WIDTH, 0);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 8525e36..592045c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -245,6 +245,7 @@
});
}
+ @SuppressWarnings("ReturnValueIgnored")
private void maybeRequestShowInlineSuggestions(int sessionId,
@Nullable InlineSuggestionsRequest request,
@Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState,
diff --git a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
new file mode 100644
index 0000000..042bcbd
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.backup;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.provider.DeviceConfig;
+
+/**
+ * Retrieves values of feature flags.
+ *
+ * <p>These flags are intended to be configured server-side and their values should be set in {@link
+ * DeviceConfig} by a service that periodically syncs with the server.
+ *
+ * <p>This class must ensure that the namespace, flag name, and default value passed into {@link
+ * DeviceConfig} matches what's declared on the server. The namespace is shared for all backup and
+ * restore flags.
+ */
+public class BackupAndRestoreFeatureFlags {
+ private static final String NAMESPACE = "backup_and_restore";
+
+ private BackupAndRestoreFeatureFlags() {}
+
+ /** Retrieves the value of the flag "backup_transport_future_timeout_millis". */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static long getBackupTransportFutureTimeoutMillis() {
+ return DeviceConfig.getLong(
+ NAMESPACE,
+ /* name= */ "backup_transport_future_timeout_millis",
+ /* defaultValue= */ 600000); // 10 minutes
+ }
+
+ /** Retrieves the value of the flag "backup_transport_callback_timeout_millis". */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static long getBackupTransportCallbackTimeoutMillis() {
+ return DeviceConfig.getLong(
+ NAMESPACE,
+ /* name= */ "backup_transport_callback_timeout_millis",
+ /* defaultValue= */ 300000); // 5 minutes
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java
index 466f647..8f73436 100644
--- a/services/backup/java/com/android/server/backup/OperationStorage.java
+++ b/services/backup/java/com/android/server/backup/OperationStorage.java
@@ -153,4 +153,4 @@
* @return a set of operation tokens for operations in that state.
*/
Set<Integer> operationTokensForOpState(@OpState int state);
-};
+}
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
index 6908c60..a94167e 100644
--- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -353,4 +353,4 @@
op.callback.handleCancel(cancelAll);
}
}
-};
+}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 40d7cad..21005bb 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -30,6 +30,7 @@
import com.android.internal.backup.IBackupTransport;
import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
import java.util.ArrayDeque;
import java.util.HashSet;
@@ -385,7 +386,8 @@
private <T> T getFutureResult(AndroidFuture<T> future) {
try {
- return future.get(600, TimeUnit.SECONDS);
+ return future.get(BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis(),
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException
| CancellationException e) {
Slog.w(TAG, "Failed to get result from transport:", e);
diff --git a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
index fb98825..deaa86c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
@@ -23,13 +23,13 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.ITransportStatusCallback;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
public class TransportStatusCallback extends ITransportStatusCallback.Stub {
private static final String TAG = "TransportStatusCallback";
- private static final int TIMEOUT_MILLIS = 300 * 1000; // 5 minutes.
private static final int OPERATION_STATUS_DEFAULT = 0;
- private final int mOperationTimeout;
+ private final long mOperationTimeout;
@GuardedBy("this")
private int mOperationStatus = OPERATION_STATUS_DEFAULT;
@@ -37,7 +37,7 @@
private boolean mHasCompletedOperation = false;
public TransportStatusCallback() {
- mOperationTimeout = TIMEOUT_MILLIS;
+ mOperationTimeout = BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis();
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index a614b72..b04f3c5 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -20,6 +20,8 @@
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
+import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
import static android.content.ComponentName.createRelative;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
@@ -40,7 +42,6 @@
import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
-import android.companion.CompanionDeviceManager;
import android.companion.IAssociationRequestCallback;
import android.content.ComponentName;
import android.content.Context;
@@ -348,8 +349,7 @@
// Send the association back via the app's callback
if (callback != null) {
try {
- // TODO: update to INTERNAL_ERROR once it's added.
- callback.onFailure(CompanionDeviceManager.REASON_CANCELED);
+ callback.onFailure(REASON_INTERNAL_ERROR);
} catch (RemoteException ignore) {
}
}
@@ -358,7 +358,7 @@
// back to the app via Activity.setResult().
if (resultReceiver != null) {
final Bundle data = new Bundle();
- resultReceiver.send(CompanionDeviceManager.RESULT_INTERNAL_ERROR, data);
+ resultReceiver.send(RESULT_INTERNAL_ERROR, data);
}
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 000bafe..ce7854d 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -86,6 +86,15 @@
}
/**
+ * For communicating when activities are blocked from entering PIP on the display by this
+ * policy controller.
+ */
+ public interface PipBlockedCallback {
+ /** Called when an activity is blocked from entering PIP. */
+ void onEnteringPipBlocked(int uid);
+ }
+
+ /**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
*/
@@ -112,6 +121,7 @@
@GuardedBy("mGenericWindowPolicyControllerLock")
final ArraySet<Integer> mRunningUids = new ArraySet<>();
@Nullable private final ActivityListener mActivityListener;
+ @Nullable private final PipBlockedCallback mPipBlockedCallback;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
@@ -155,6 +165,7 @@
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
@NonNull ActivityListener activityListener,
+ @NonNull PipBlockedCallback pipBlockedCallback,
@NonNull ActivityBlockedCallback activityBlockedCallback,
@NonNull SecureWindowCallback secureWindowCallback,
@AssociationRequest.DeviceProfile String deviceProfile) {
@@ -169,6 +180,7 @@
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
mDeviceProfile = deviceProfile;
+ mPipBlockedCallback = pipBlockedCallback;
mSecureWindowCallback = secureWindowCallback;
}
@@ -317,6 +329,17 @@
}
}
+ @Override
+ public boolean isEnteringPipAllowed(int uid) {
+ if (super.isEnteringPipAllowed(uid)) {
+ return true;
+ }
+ mHandler.post(() -> {
+ mPipBlockedCallback.onEnteringPipBlocked(uid);
+ });
+ return false;
+ }
+
/**
* Returns true if an app with the given UID has an activity running on the virtual display for
* this controller.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5ebbf07..fbde9e0 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -227,6 +227,12 @@
return mParams.getName();
}
+ /** Returns the policy specified for this policy type */
+ public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+ @VirtualDeviceParams.PolicyType int policyType) {
+ return mParams.getDevicePolicy(policyType);
+ }
+
/** Returns the unique device ID of this device. */
@Override // Binder call
public int getDeviceId() {
@@ -624,6 +630,7 @@
mParams.getBlockedActivities(),
mParams.getDefaultActivityPolicy(),
createListenerAdapter(),
+ this::onEnteringPipBlocked,
this::onActivityBlocked,
this::onSecureWindowShown,
mAssociationInfo.getDeviceProfile());
@@ -779,6 +786,11 @@
return mVirtualDisplayIds.contains(displayId);
}
+ void onEnteringPipBlocked(int uid) {
+ showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
+ Toast.LENGTH_LONG, mContext.getMainLooper());
+ }
+
interface OnDeviceCloseListener {
void onClose(int associationId);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index c400a74..a8797a0 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -233,6 +233,13 @@
mLocalService.onAppsOnVirtualDeviceChanged();
}
+ @VisibleForTesting
+ void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+ }
+ }
+
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
VirtualDeviceImpl.PendingTrampolineCallback {
@@ -358,6 +365,12 @@
return virtualDevices;
}
+ @Override // BinderCall
+ @VirtualDeviceParams.DevicePolicy
+ public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+ return mLocalService.getDevicePolicy(deviceId, policyType);
+ }
+
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final int callingUserId = getCallingUserHandle().getIdentifier();
@@ -439,6 +452,20 @@
}
@Override
+ @VirtualDeviceParams.DevicePolicy
+ public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+ if (device.getDeviceId() == deviceId) {
+ return device.getDevicePolicy(policyType);
+ }
+ }
+ }
+ return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+ }
+
+ @Override
public void onVirtualDisplayCreated(int displayId) {
final VirtualDisplayListener[] listeners;
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 0f101b0..08ee6d7 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -658,7 +658,6 @@
int sessionId, int flags, @NonNull IResultReceiver result) {
Objects.requireNonNull(activityToken);
Objects.requireNonNull(shareableActivityToken);
- Objects.requireNonNull(sessionId);
final int userId = UserHandle.getCallingUserId();
final ActivityPresentationInfo activityPresentationInfo = getAmInternal()
@@ -677,7 +676,6 @@
@Override
public void finishSession(int sessionId) {
- Objects.requireNonNull(sessionId);
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 553146d..088eddb 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -100,7 +100,7 @@
name: "services.core.unboosted",
defaults: ["platform_service_defaults"],
srcs: [
- ":android.hardware.biometrics.face-V2-java-source",
+ ":android.hardware.biometrics.face-V3-java-source",
":statslog-art-java-gen",
":statslog-contexthub-java-gen",
":services.core-sources",
@@ -161,14 +161,13 @@
"android.hardware.weaver-V1.0-java",
"android.hardware.biometrics.face-V1.0-java",
"android.hardware.biometrics.fingerprint-V2.3-java",
- "android.hardware.biometrics.fingerprint-V2-java",
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.1-java",
"android.hardware.ir-V1-java",
"android.hardware.rebootescrow-V1-java",
"android.hardware.soundtrigger-V2.3-java",
"android.hardware.power.stats-V1-java",
- "android.hardware.power-V3-java",
+ "android.hardware.power-V4-java",
"android.hidl.manager-V1.2-java",
"capture_state_listener-aidl-java",
"icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 4278b3e..71a4c73 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1306,6 +1306,13 @@
}
@Override
+ public int getBatteryHealth() {
+ synchronized (mLock) {
+ return mHealthInfo.batteryHealth;
+ }
+ }
+
+ @Override
public boolean getBatteryLevelLow() {
synchronized (mLock) {
return mBatteryLevelLow;
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 8055afc..544dd4e 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,16 +26,21 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.InstallSourceInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -43,6 +48,8 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.PackageUtils;
import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
@@ -54,14 +61,16 @@
import libcore.util.HexEncoding;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@@ -82,10 +91,36 @@
@VisibleForTesting
static final String BINARY_HASH_ERROR = "SHA256HashError";
+ static final int MEASURE_APEX_AND_MODULES = 1;
+ static final int MEASURE_PRELOADS = 2;
+ static final int MEASURE_NEW_MBAS = 3;
+
+ static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;
+
+ @VisibleForTesting
+ static final String BUNDLE_PACKAGE_INFO = "package-info";
+ @VisibleForTesting
+ static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo";
+ @VisibleForTesting
+ static final String BUNDLE_CONTENT_DIGEST = "content-digest";
+
+ // used for indicating any type of error during MBA measurement
+ static final int MBA_STATUS_ERROR = 0;
+ // used for indicating factory condition preloads
+ static final int MBA_STATUS_PRELOADED = 1;
+ // used for indicating preloaded apps that are updated
+ static final int MBA_STATUS_UPDATED_PRELOAD = 2;
+ // used for indicating newly installed MBAs
+ static final int MBA_STATUS_NEW_INSTALL = 3;
+ // used for indicating newly installed MBAs that are updated (but unused currently)
+ static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+
+ private static final boolean DEBUG = true; // set this to false upon submission
+
private final Context mContext;
private String mVbmetaDigest;
- private HashMap<String, String> mBinaryHashes;
- private HashMap<String, Long> mBinaryLastUpdateTimes;
+ // the system time (in ms) the last measurement was taken
+ private long mMeasurementsLastRecordedMs;
final class BinaryTransparencyServiceImpl extends IBinaryTransparencyService.Stub {
@@ -95,25 +130,298 @@
}
@Override
- public Map getApexInfo() {
- HashMap results = new HashMap();
- if (!updateBinaryMeasurements()) {
- Slog.e(TAG, "Error refreshing APEX measurements.");
- return results;
- }
- PackageManager pm = mContext.getPackageManager();
- if (pm == null) {
- Slog.e(TAG, "Error obtaining an instance of PackageManager.");
- return results;
- }
+ public List getApexInfo() {
+ List<Bundle> results = new ArrayList<>();
- for (PackageInfo packageInfo : getInstalledApexs()) {
- results.put(packageInfo, mBinaryHashes.get(packageInfo.packageName));
+ for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
+ Bundle apexMeasurement = measurePackage(packageInfo);
+ results.add(apexMeasurement);
}
return results;
}
+ /**
+ * A helper function to compute the SHA256 digest of APK package signer.
+ * @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}.
+ * @return an array of {@code String} representing hex encoded string of the
+ * SHA256 digest of APK signer(s). The number of signers will be reflected by the
+ * size of the array.
+ * However, {@code null} is returned if there is any error.
+ */
+ private String[] computePackageSignerSha256Digests(@Nullable SigningInfo signingInfo) {
+ if (signingInfo == null) {
+ Slog.e(TAG, "signingInfo is null");
+ return null;
+ }
+
+ Signature[] packageSigners = signingInfo.getApkContentsSigners();
+ List<String> resultList = new ArrayList<>();
+ for (Signature packageSigner : packageSigners) {
+ byte[] digest = PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+ String digestHexString = HexEncoding.encodeToString(digest, false);
+ resultList.add(digestHexString);
+ }
+ return resultList.toArray(new String[1]);
+ }
+
+ /**
+ * Perform basic measurement (i.e. content digest) on a given package.
+ * @param packageInfo The package to be measured.
+ * @return a {@link android.os.Bundle} that packs the measurement result with the following
+ * keys: {@link #BUNDLE_PACKAGE_INFO},
+ * {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM}
+ * {@link #BUNDLE_CONTENT_DIGEST}
+ */
+ private @NonNull Bundle measurePackage(PackageInfo packageInfo) {
+ Bundle result = new Bundle();
+
+ // compute content digest
+ if (DEBUG) {
+ Slog.d(TAG, "Computing content digest for " + packageInfo.packageName + " at "
+ + packageInfo.applicationInfo.sourceDir);
+ }
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+ packageInfo.applicationInfo.sourceDir);
+ result.putParcelable(BUNDLE_PACKAGE_INFO, packageInfo);
+ if (contentDigests == null) {
+ Slog.d(TAG, "Failed to compute content digest for "
+ + packageInfo.applicationInfo.sourceDir);
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+ return result;
+ }
+
+ // in this iteration, we'll be supporting only 2 types of digests:
+ // CHUNKED_SHA256 and CHUNKED_SHA512.
+ // And only one of them will be available per package.
+ if (contentDigests.containsKey(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)) {
+ Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ } else if (contentDigests.containsKey(
+ ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) {
+ Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ } else {
+ // TODO(b/259423111): considering putting the raw values for the algorithm & digest
+ // into the bundle to track potential other digest algorithms that may be in use
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Measures and records digests for *all* covered binaries/packages.
+ *
+ * This method will be called in a Job scheduled to take measurements periodically.
+ *
+ * Packages that are covered so far are:
+ * - all APEXs (introduced in Android T)
+ * - all mainline modules (introduced in Android T)
+ * - all preloaded apps and their update(s) (new in Android U)
+ * - dynamically installed mobile bundled apps (MBAs) (new in Android U)
+ *
+ * @return a {@code List<Bundle>}. Each Bundle item contains values as
+ * defined by the return value of {@link #measurePackage(PackageInfo)}.
+ */
+ public List getMeasurementsForAllPackages() {
+ List<Bundle> results = new ArrayList<>();
+ PackageManager pm = mContext.getPackageManager();
+ Set<String> packagesMeasured = new HashSet<>();
+
+ // check if we should record the resulting measurements
+ long currentTimeMs = System.currentTimeMillis();
+ boolean record = false;
+ if ((currentTimeMs - mMeasurementsLastRecordedMs) >= RECORD_MEASUREMENTS_COOLDOWN_MS) {
+ Slog.d(TAG, "Measurement was last taken at " + mMeasurementsLastRecordedMs
+ + " and is now updated to: " + currentTimeMs);
+ mMeasurementsLastRecordedMs = currentTimeMs;
+ record = true;
+ }
+
+ // measure all APEXs first
+ if (DEBUG) {
+ Slog.d(TAG, "Measuring APEXs...");
+ }
+ for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
+ packagesMeasured.add(packageInfo.packageName);
+
+ Bundle apexMeasurement = measurePackage(packageInfo);
+ results.add(apexMeasurement);
+
+ if (record) {
+ // compute digests of signing info
+ String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+ packageInfo.signingInfo);
+
+ // log to Westworld
+ FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
+ packageInfo.packageName,
+ packageInfo.getLongVersionCode(),
+ HexEncoding.encodeToString(apexMeasurement.getByteArray(
+ BUNDLE_CONTENT_DIGEST), false),
+ apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+ signerDigestHexStrings);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages after considering APEXs.");
+ }
+
+ // proceed with all preloaded apps
+ for (PackageInfo packageInfo : pm.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY
+ | PackageManager.GET_SIGNING_CERTIFICATES))) {
+ if (packagesMeasured.contains(packageInfo.packageName)) {
+ continue;
+ }
+ packagesMeasured.add(packageInfo.packageName);
+
+ int mba_status = MBA_STATUS_PRELOADED;
+ if (packageInfo.signingInfo == null) {
+ Slog.d(TAG, "Preload " + packageInfo.packageName + " at "
+ + packageInfo.applicationInfo.sourceDir + " has likely been updated.");
+ mba_status = MBA_STATUS_UPDATED_PRELOAD;
+
+ PackageInfo origPackageInfo = packageInfo;
+ try {
+ packageInfo = pm.getPackageInfo(packageInfo.packageName,
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL
+ | PackageManager.GET_SIGNING_CERTIFICATES));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Failed to obtain an updated PackageInfo of "
+ + origPackageInfo.packageName, e);
+ packageInfo = origPackageInfo;
+ mba_status = MBA_STATUS_ERROR;
+ }
+ }
+
+
+ Bundle packageMeasurement = measurePackage(packageInfo);
+ results.add(packageMeasurement);
+
+ if (record) {
+ // compute digests of signing info
+ String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+ packageInfo.signingInfo);
+
+ // now we should have all the bits for the atom
+ /* TODO: Uncomment and test after merging new atom definition.
+ FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
+ packageInfo.packageName,
+ packageInfo.getLongVersionCode(),
+ HexEncoding.encodeToString(packageMeasurement.getByteArray(
+ BUNDLE_CONTENT_DIGEST), false),
+ packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+ signerDigestHexStrings, // signer_cert_digest
+ mba_status, // mba_status
+ null, // initiator
+ null, // initiator_signer_digest
+ null, // installer
+ null // originator
+ );
+ */
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages after considering preloads");
+ }
+
+ // lastly measure all newly installed MBAs
+ for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
+ if (packagesMeasured.contains(packageInfo.packageName)) {
+ continue;
+ }
+ packagesMeasured.add(packageInfo.packageName);
+
+ Bundle packageMeasurement = measurePackage(packageInfo);
+ results.add(packageMeasurement);
+
+ if (record) {
+ // compute digests of signing info
+ String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+ packageInfo.signingInfo);
+
+ // then extract package's InstallSourceInfo
+ if (DEBUG) {
+ Slog.d(TAG, "Extracting InstallSourceInfo for " + packageInfo.packageName);
+ }
+ InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+ packageInfo.packageName);
+ String initiator = null;
+ SigningInfo initiatorSignerInfo = null;
+ String[] initiatorSignerInfoDigest = null;
+ String installer = null;
+ String originator = null;
+
+ if (installSourceInfo != null) {
+ initiator = installSourceInfo.getInitiatingPackageName();
+ initiatorSignerInfo = installSourceInfo.getInitiatingPackageSigningInfo();
+ if (initiatorSignerInfo != null) {
+ initiatorSignerInfoDigest = computePackageSignerSha256Digests(
+ initiatorSignerInfo);
+ }
+ installer = installSourceInfo.getInstallingPackageName();
+ originator = installSourceInfo.getOriginatingPackageName();
+ }
+
+ // we should now have all the info needed for the atom
+ /* TODO: Uncomment and test after merging new atom definition.
+ FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
+ packageInfo.packageName,
+ packageInfo.getLongVersionCode(),
+ HexEncoding.encodeToString(packageMeasurement.getByteArray(
+ BUNDLE_CONTENT_DIGEST), false),
+ packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+ signerDigestHexStrings,
+ MBA_STATUS_NEW_INSTALL, // mba_status
+ initiator,
+ initiatorSignerInfoDigest,
+ installer,
+ originator
+ );
+ */
+ }
+ }
+ if (DEBUG) {
+ long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages altogether in " + timeSpentMeasuring + "ms");
+ }
+
+ return results;
+ }
+
+ /**
+ * A wrapper around
+ * {@link ApkSignatureVerifier#verifySignaturesInternal(ParseInput, String, int, boolean)}.
+ * @param pathToApk The APK's installation path
+ * @return a {@code Map<Integer, byte[]>} with algorithm type as the key and content
+ * digest as the value.
+ * a {@code null} is returned upon encountering any error.
+ */
+ private Map<Integer, byte[]> computeApkContentDigest(String pathToApk) {
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+ ApkSignatureVerifier.verifySignaturesInternal(input,
+ pathToApk,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2, false);
+ if (parseResult.isError()) {
+ Slog.e(TAG, "Failed to compute content digest for "
+ + pathToApk + " due to: "
+ + parseResult.getErrorMessage());
+ return null;
+ }
+ return parseResult.getResult().contentDigests;
+ }
+
@Override
public void onShellCommand(@Nullable FileDescriptor in,
@Nullable FileDescriptor out,
@@ -165,43 +473,36 @@
private void printPackageMeasurements(PackageInfo packageInfo,
final PrintWriter pw) {
- pw.print(mBinaryHashes.get(packageInfo.packageName) + ",");
- // TODO: To be moved to somewhere more suitable
- final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
- ApkSignatureVerifier.verifySignaturesInternal(input,
- packageInfo.applicationInfo.sourceDir,
- SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, false);
- if (parseResult.isError()) {
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+ packageInfo.applicationInfo.sourceDir);
+ if (contentDigests == null) {
pw.println("ERROR: Failed to compute package content digest for "
- + packageInfo.applicationInfo.sourceDir + "due to: "
- + parseResult.getErrorMessage());
- } else {
- ApkSignatureVerifier.SigningDetailsWithDigests signingDetails =
- parseResult.getResult();
- Map<Integer, byte[]> contentDigests = signingDetails.contentDigests;
- for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
- Integer algorithmId = entry.getKey();
- byte[] cDigest = entry.getValue();
- //pw.print("Content digest algorithm: ");
- switch (algorithmId) {
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
- pw.print("CHUNKED_SHA256:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
- pw.print("CHUNKED_SHA512:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
- pw.print("VERITY_CHUNKED_SHA256:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
- pw.print("SHA256:");
- break;
- default:
- pw.print("UNKNOWN:");
- }
- pw.print(HexEncoding.encodeToString(cDigest, false));
+ + packageInfo.applicationInfo.sourceDir);
+ return;
+ }
+
+ for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+ Integer algorithmId = entry.getKey();
+ byte[] contentDigest = entry.getValue();
+
+ // TODO(b/259348134): consider refactoring the following to a helper method
+ switch (algorithmId) {
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+ pw.print("CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+ pw.print("CHUNKED_SHA512:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ pw.print("VERITY_CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+ pw.print("SHA256:");
+ break;
+ default:
+ pw.print("UNKNOWN_ALGO_ID(" + algorithmId + "):");
}
+ pw.print(HexEncoding.encodeToString(contentDigest, false));
}
}
@@ -211,14 +512,49 @@
pw.println("Current install location: "
+ packageInfo.applicationInfo.sourceDir);
if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
- String origPackageFilepath = getOriginalPreinstalledLocation(
+ String origPackageFilepath = getOriginalApexPreinstalledLocation(
packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
- pw.println("|--> Package pre-installed location: " + origPackageFilepath);
- String digest = mBinaryHashes.get(origPackageFilepath);
- if (digest == null) {
- pw.println("ERROR finding SHA256-digest from cache...");
+ pw.println("|--> Pre-installed package install location: "
+ + origPackageFilepath);
+
+ // TODO(b/259347186): revive this with the proper cmd options.
+ /*
+ String digest = PackageUtils.computeSha256DigestForLargeFile(
+ origPackageFilepath, PackageUtils.createLargeFileBuffer());
+ */
+
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+ origPackageFilepath);
+ if (contentDigests == null) {
+ pw.println("ERROR: Failed to compute package content digest for "
+ + origPackageFilepath);
} else {
- pw.println("|--> Pre-installed package SHA256-digest: " + digest);
+ // TODO(b/259348134): consider refactoring this to a helper method
+ for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+ Integer algorithmId = entry.getKey();
+ byte[] contentDigest = entry.getValue();
+ pw.print("|--> Pre-installed package content digest algorithm: ");
+ switch (algorithmId) {
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+ pw.print("CHUNKED_SHA256");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+ pw.print("CHUNKED_SHA512");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ pw.print("VERITY_CHUNKED_SHA256");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+ pw.print("SHA256");
+ break;
+ default:
+ pw.print("UNKNOWN");
+ }
+ pw.print("\n");
+ pw.print("|--> Pre-installed package content digest: ");
+ pw.print(HexEncoding.encodeToString(contentDigest, false));
+ pw.print("\n");
+ }
}
}
pw.println("First install time (ms): " + packageInfo.firstInstallTime);
@@ -281,21 +617,90 @@
+ (moduleInfo.isHidden() ? "hidden" : "visible"));
}
+ private void printAppDetails(PackageInfo packageInfo,
+ boolean printLibraries,
+ final PrintWriter pw) {
+ pw.println("--- App Details ---");
+ pw.println("Name: " + packageInfo.applicationInfo.name);
+ pw.println("Label: " + mContext.getPackageManager().getApplicationLabel(
+ packageInfo.applicationInfo));
+ pw.println("Description: " + packageInfo.applicationInfo.loadDescription(
+ mContext.getPackageManager()));
+ pw.println("Has code: " + packageInfo.applicationInfo.hasCode());
+ pw.println("Is enabled: " + packageInfo.applicationInfo.enabled);
+ pw.println("Is suspended: " + ((packageInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_SUSPENDED) != 0));
+
+ pw.println("Compile SDK version: " + packageInfo.compileSdkVersion);
+ pw.println("Target SDK version: "
+ + packageInfo.applicationInfo.targetSdkVersion);
+
+ pw.println("Is privileged: "
+ + packageInfo.applicationInfo.isPrivilegedApp());
+ pw.println("Is a stub: " + packageInfo.isStub);
+ pw.println("Is a core app: " + packageInfo.coreApp);
+ pw.println("SEInfo: " + packageInfo.applicationInfo.seInfo);
+ pw.println("Component factory: "
+ + packageInfo.applicationInfo.appComponentFactory);
+ pw.println("Process name: " + packageInfo.applicationInfo.processName);
+ pw.println("Task affinity : " + packageInfo.applicationInfo.taskAffinity);
+ pw.println("UID: " + packageInfo.applicationInfo.uid);
+ pw.println("Shared UID: " + packageInfo.sharedUserId);
+
+ if (printLibraries) {
+ pw.println("== App's Shared Libraries ==");
+ List<SharedLibraryInfo> sharedLibraryInfos =
+ packageInfo.applicationInfo.getSharedLibraryInfos();
+ if (sharedLibraryInfos == null || sharedLibraryInfos.isEmpty()) {
+ pw.println("<none>");
+ }
+
+ for (int i = 0; i < sharedLibraryInfos.size(); i++) {
+ SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.get(i);
+ pw.println(" ++ Library #" + (i + 1) + " ++");
+ pw.println(" Lib name: " + sharedLibraryInfo.getName());
+ long libVersion = sharedLibraryInfo.getLongVersion();
+ pw.print(" Lib version: ");
+ if (libVersion == SharedLibraryInfo.VERSION_UNDEFINED) {
+ pw.print("undefined");
+ } else {
+ pw.print(libVersion);
+ }
+ pw.print("\n");
+
+ pw.println(" Lib package name (if available): "
+ + sharedLibraryInfo.getPackageName());
+ pw.println(" Lib path: " + sharedLibraryInfo.getPath());
+ pw.print(" Lib type: ");
+ switch (sharedLibraryInfo.getType()) {
+ case SharedLibraryInfo.TYPE_BUILTIN:
+ pw.print("built-in");
+ break;
+ case SharedLibraryInfo.TYPE_DYNAMIC:
+ pw.print("dynamic");
+ break;
+ case SharedLibraryInfo.TYPE_STATIC:
+ pw.print("static");
+ break;
+ case SharedLibraryInfo.TYPE_SDK_PACKAGE:
+ pw.print("SDK");
+ break;
+ case SharedLibraryInfo.VERSION_UNDEFINED:
+ default:
+ pw.print("undefined");
+ break;
+ }
+ pw.print("\n");
+ pw.println(" Is a native lib: " + sharedLibraryInfo.isNative());
+ }
+ }
+
+ }
+
private int printAllApexs() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
-
- // refresh cache to make sure info is most up-to-date
- if (!updateBinaryMeasurements()) {
- pw.println("ERROR: Failed to refresh info for APEXs.");
- return -1;
- }
- if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
- pw.println("ERROR: Unable to obtain apex_info at this time.");
- return -1;
- }
-
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
@@ -315,13 +720,15 @@
if (!verbose) {
pw.println("APEX Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
- for (PackageInfo packageInfo : getInstalledApexs()) {
+ for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
if (verbose) {
pw.println("APEX Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
String packageName = packageInfo.packageName;
@@ -352,17 +759,6 @@
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
-
- // refresh cache to make sure info is most up-to-date
- if (!updateBinaryMeasurements()) {
- pw.println("ERROR: Failed to refresh info for Modules.");
- return -1;
- }
- if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
- pw.println("ERROR: Unable to obtain module_info at this time.");
- return -1;
- }
-
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
@@ -382,14 +778,16 @@
if (!verbose) {
pw.println("Module Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
if (verbose) {
pw.println("Module Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
try {
@@ -421,6 +819,72 @@
return 0;
}
+ private int printAllMbas() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean verbose = false;
+ boolean printLibraries = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ verbose = true;
+ break;
+ case "-l":
+ printLibraries = true;
+ break;
+ default:
+ pw.println("ERROR: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ if (!verbose) {
+ pw.println("MBA Info [Format: package_name,package_version,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
+ for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
+ if (verbose) {
+ pw.println("MBA Info [Format: package_name,package_version,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
+ pw.print(packageInfo.packageName + ",");
+ pw.print(packageInfo.getLongVersionCode() + ",");
+ printPackageMeasurements(packageInfo, pw);
+ pw.print("\n");
+
+ if (verbose) {
+ printAppDetails(packageInfo, printLibraries, pw);
+ printPackageInstallationInfo(packageInfo, pw);
+ printPackageSignerDetails(packageInfo.signingInfo, pw);
+ pw.println("");
+ }
+ }
+ return 0;
+ }
+
+ // TODO(b/259347186): add option handling full file-based SHA256 digest
+ private int printAllPreloads() {
+ final PrintWriter pw = getOutPrintWriter();
+
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Slog.e(TAG, "Failed to obtain PackageManager.");
+ return -1;
+ }
+ List<PackageInfo> factoryApps = pm.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY));
+
+ pw.println("Preload Info [Format: package_name]");
+ for (PackageInfo packageInfo : factoryApps) {
+ pw.println(packageInfo.packageName);
+ }
+ return 0;
+ }
+
@Override
public int onCommand(String cmd) {
if (cmd == null) {
@@ -443,6 +907,10 @@
return printAllApexs();
case "module_info":
return printAllModules();
+ case "mba_info":
+ return printAllMbas();
+ case "preload_info":
+ return printAllPreloads();
default:
pw.println(String.format("ERROR: Unknown info type '%s'",
infoType));
@@ -466,11 +934,18 @@
pw.println("");
pw.println(" get apex_info [-v]");
pw.println(" Print information about installed APEXs on device.");
- pw.println(" -v: lists more verbose information about each APEX");
+ pw.println(" -v: lists more verbose information about each APEX.");
pw.println("");
pw.println(" get module_info [-v]");
pw.println(" Print information about installed modules on device.");
- pw.println(" -v: lists more verbose information about each module");
+ pw.println(" -v: lists more verbose information about each module.");
+ pw.println("");
+ pw.println(" get mba_info [-v] [-l]");
+ pw.println(" Print information about installed mobile bundle apps "
+ + "(MBAs on device).");
+ pw.println(" -v: lists more verbose information about each app.");
+ pw.println(" -l: lists shared library info. This will only be "
+ + "listed with -v");
pw.println("");
}
@@ -488,8 +963,7 @@
mContext = context;
mServiceImpl = new BinaryTransparencyServiceImpl();
mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
- mBinaryHashes = new HashMap<>();
- mBinaryLastUpdateTimes = new HashMap<>();
+ mMeasurementsLastRecordedMs = 0;
}
/**
@@ -520,44 +994,43 @@
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
- // due to potentially long computation that holds up boot time, computations for
- // SHA256 digests of APEX and Module packages are scheduled here,
- // but only executed when device is idle.
- Slog.i(TAG, "Scheduling APEX and Module measurements to be updated.");
+ // to avoid the risk of holding up boot time, computations to measure APEX, Module, and
+ // MBA digests are scheduled here, but only executed when the device is idle and plugged
+ // in.
+ Slog.i(TAG, "Scheduling measurements to be taken.");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
}
}
/**
- * JobService to update binary measurements and update internal cache.
+ * JobService to measure all covered binaries and record result to Westworld.
*/
public static class UpdateMeasurementsJobService extends JobService {
- private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID =
- BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode();
+ private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
+ UpdateMeasurementsJobService.class.hashCode();
@Override
public boolean onStartJob(JobParameters params) {
Slog.d(TAG, "Job to update binary measurements started.");
- if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) {
+ if (params.getJobId() != DO_BINARY_MEASUREMENTS_JOB_ID) {
return false;
}
- // we'll still update the measurements via threads to be mindful of low-end devices
+ // we'll perform binary measurements via threads to be mindful of low-end devices
// where this operation might take longer than expected, and so that we don't block
// system_server's main thread.
Executors.defaultThreadFactory().newThread(() -> {
- // since we can't call updateBinaryMeasurements() directly, calling
- // getApexInfo() achieves the same effect, and we simply discard the return
- // value
-
+ // we discard the return value of getMeasurementsForAllPackages() as the
+ // results of the measurements will be recorded, and that is what we're aiming
+ // for with this job.
IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
IBinaryTransparencyService iBtsService =
IBinaryTransparencyService.Stub.asInterface(b);
try {
- iBtsService.getApexInfo();
+ iBtsService.getMeasurementsForAllPackages();
} catch (RemoteException e) {
- Slog.e(TAG, "Updating binary measurements was interrupted.", e);
+ Slog.e(TAG, "Taking binary measurements was interrupted.", e);
return;
}
jobFinished(params, false);
@@ -573,25 +1046,26 @@
@SuppressLint("DefaultLocale")
static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
- Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job");
+ Slog.i(TAG, "Scheduling binary content-digest computation job");
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler == null) {
Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
return;
}
- final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID,
+ final JobInfo jobInfo = new JobInfo.Builder(DO_BINARY_MEASUREMENTS_JOB_ID,
new ComponentName(context, UpdateMeasurementsJobService.class))
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
+ .setPeriodic(RECORD_MEASUREMENTS_COOLDOWN_MS)
.build();
if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
- Slog.e(TAG, "Failed to schedule job to update binary measurements.");
+ Slog.e(TAG, "Failed to schedule job to measure binaries.");
return;
}
- Slog.d(TAG, String.format(
- "Job %d to update binary measurements scheduled successfully.",
- COMPUTE_APEX_MODULE_SHA256_JOB_ID));
+ Slog.d(TAG, TextUtils.formatSimple(
+ "Job %d to measure binaries was scheduled successfully.",
+ DO_BINARY_MEASUREMENTS_JOB_ID));
}
}
@@ -602,7 +1076,7 @@
}
@NonNull
- private List<PackageInfo> getInstalledApexs() {
+ private List<PackageInfo> getCurrentInstalledApexs() {
List<PackageInfo> results = new ArrayList<>();
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
@@ -636,162 +1110,52 @@
}
}
-
- /**
- * Updates the internal data structure with the most current APEX measurements.
- * @return true if update is successful; false otherwise.
- */
- private boolean updateBinaryMeasurements() {
- if (mBinaryHashes.size() == 0) {
- Slog.d(TAG, "No apex in cache yet.");
- doFreshBinaryMeasurements();
- return true;
- }
-
- PackageManager pm = mContext.getPackageManager();
- if (pm == null) {
- Slog.e(TAG, "Failed to obtain a valid PackageManager instance.");
- return false;
- }
-
- // We're assuming updates to existing modules and APEXs can happen, but not brand new
- // ones appearing out of the blue. Thus, we're going to only go through our cache to check
- // for changes, rather than freshly invoking `getInstalledPackages()` and
- // `getInstalledModules()`
- byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
- for (Map.Entry<String, Long> entry : mBinaryLastUpdateTimes.entrySet()) {
- String packageName = entry.getKey();
- try {
- PackageInfo packageInfo = pm.getPackageInfo(packageName,
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
- long cachedUpdateTime = entry.getValue();
-
- if (packageInfo.lastUpdateTime > cachedUpdateTime) {
- Slog.d(TAG, packageName + " has been updated!");
- entry.setValue(packageInfo.lastUpdateTime);
-
- // compute the digest for the updated package
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
- packageInfo.applicationInfo.sourceDir, largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, "Failed to compute SHA256sum for file at "
- + packageInfo.applicationInfo.sourceDir);
- mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(packageName, sha256digest);
- }
-
- if (packageInfo.isApex) {
- FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
- packageInfo.packageName,
- packageInfo.getLongVersionCode(),
- mBinaryHashes.get(packageInfo.packageName));
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Could not find package with name " + packageName);
- continue;
- }
- }
-
- return true;
- }
-
- private String getOriginalPreinstalledLocation(String packageName,
+ // TODO(b/259349011): Need to be more robust against package name mismatch in the filename
+ private String getOriginalApexPreinstalledLocation(String packageName,
String currentInstalledLocation) {
if (currentInstalledLocation.contains("/decompressed/")) {
- return "/system/apex/" + packageName + ".capex";
+ String resultPath = "system/apex" + packageName + ".capex";
+ File f = new File(resultPath);
+ if (f.exists()) {
+ return resultPath;
+ }
+ return "/system/apex/" + packageName + ".next.capex";
}
return "/system/apex" + packageName + "apex";
}
- private void doFreshBinaryMeasurements() {
- PackageManager pm = mContext.getPackageManager();
- Slog.d(TAG, "Obtained package manager");
-
- // In general, we care about all APEXs, *and* all Modules, which may include some APKs.
-
- // First, we deal with all installed APEXs.
- byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
- for (PackageInfo packageInfo : getInstalledApexs()) {
- ApplicationInfo appInfo = packageInfo.applicationInfo;
-
- // compute SHA256 for these APEXs
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir,
- largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
- packageInfo.packageName));
- mBinaryHashes.put(packageInfo.packageName, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(packageInfo.packageName, sha256digest);
- }
- FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, packageInfo.packageName,
- packageInfo.getLongVersionCode(), mBinaryHashes.get(packageInfo.packageName));
- Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName,
- packageInfo.lastUpdateTime));
- mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
+ /**
+ * Wrapper method to call into IBICS to get a list of all newly installed MBAs.
+ *
+ * We expect IBICS to maintain an accurate list of installed MBAs, and we merely make use of
+ * the results within this service. This means we do not further check whether the
+ * apps in the returned slice is still installed or not, esp. considering that preloaded apps
+ * could be updated, or post-setup installed apps *might* be deleted in real time.
+ *
+ * Note that we do *not* cache the results from IBICS because of the more dynamic nature of
+ * MBAs v.s. other binaries that we measure.
+ *
+ * @return a list of preloaded apps + dynamically installed apps that fit the definition of MBA.
+ */
+ @NonNull
+ private List<PackageInfo> getNewlyInstalledMbas() {
+ List<PackageInfo> result = new ArrayList<>();
+ IBackgroundInstallControlService iBics = IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ if (iBics == null) {
+ Slog.e(TAG,
+ "Failed to obtain an IBinder instance of IBackgroundInstallControlService");
+ return result;
}
-
- for (PackageInfo packageInfo : getInstalledApexs()) {
- ApplicationInfo appInfo = packageInfo.applicationInfo;
- if (!appInfo.sourceDir.startsWith("/data/")) {
- continue;
- }
- String origInstallPath = getOriginalPreinstalledLocation(packageInfo.packageName,
- appInfo.sourceDir);
-
- // compute SHA256 for the orig /system APEXs
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(origInstallPath,
- largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, String.format("Failed to compute SHA256 digest for file at %s",
- origInstallPath));
- mBinaryHashes.put(origInstallPath, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(origInstallPath, sha256digest);
- }
- // there'd be no entry into mBinaryLastUpdateTimes
+ ParceledListSlice<PackageInfo> slice;
+ try {
+ slice = iBics.getBackgroundInstalledPackages(
+ PackageManager.MATCH_ALL | PackageManager.GET_SIGNING_CERTIFICATES,
+ UserHandle.USER_SYSTEM);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get a list of MBAs.", e);
+ return result;
}
-
- // Next, get all installed modules from PackageManager - skip over those APEXs we've
- // processed above
- for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
- String packageName = module.getPackageName();
- if (packageName == null) {
- Slog.e(TAG, "ERROR: Encountered null package name for module "
- + module.getApexModuleName());
- continue;
- }
- if (mBinaryHashes.containsKey(module.getPackageName())) {
- continue;
- }
-
- // get PackageInfo for this module
- try {
- PackageInfo packageInfo = pm.getPackageInfo(packageName,
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
- ApplicationInfo appInfo = packageInfo.applicationInfo;
-
- // compute SHA256 digest for these modules
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
- appInfo.sourceDir, largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
- packageName));
- mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(packageName, sha256digest);
- }
- Slog.d(TAG, String.format("Last update time for %s: %d", packageName,
- packageInfo.lastUpdateTime));
- mBinaryLastUpdateTimes.put(packageName, packageInfo.lastUpdateTime);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "ERROR: Could not obtain PackageInfo for package name: "
- + packageName);
- continue;
- }
- }
+ return slice.getList();
}
-
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index e529010..7d2e276 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -466,7 +466,8 @@
public static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
return isEmergencyGestureEnabled(context.getResources())
&& Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0;
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+ isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
/**
@@ -513,6 +514,11 @@
return resources.getBoolean(com.android.internal.R.bool.config_emergencyGestureEnabled);
}
+ private static boolean isDefaultEmergencyGestureEnabled(Resources resources) {
+ return resources.getBoolean(
+ com.android.internal.R.bool.config_defaultEmergencyGestureEnabled);
+ }
+
/**
* Whether GestureLauncherService should be enabled according to system properties.
*/
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index e40f001..933d259 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -473,18 +473,6 @@
}
/**
- * The {@link UserManager#isUserVisible() user visibility} changed.
- *
- * <p>This callback is called before the user starts or is switched to (or after it stops), when
- * its visibility changed because of that action.
- *
- * @hide
- */
- // NOTE: change visible to int if this method becomes a @SystemApi
- public void onUserVisibilityChanged(@NonNull TargetUser user, boolean visible) {
- }
-
- /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called prior to sending the SHUTDOWN
* broadcast to the user; it is a good place to stop making use of any resources of that
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 953e850..a05b84b 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -82,10 +82,6 @@
private static final String USER_STOPPING = "Stop"; // Logged as onUserStopping()
private static final String USER_STOPPED = "Cleanup"; // Logged as onUserStopped()
private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onUserCompletedEvent()
- private static final String USER_VISIBLE = "Visible"; // Logged on onUserVisible() and
- // onUserStarting() (when visible is true)
- private static final String USER_INVISIBLE = "Invisible"; // Logged on onUserStopping()
- // (when visibilityChanged is true)
// The default number of threads to use if lifecycle thread pool is enabled.
private static final int DEFAULT_MAX_USER_POOL_THREADS = 3;
@@ -354,58 +350,29 @@
/**
* Starts the given user.
*/
- public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId,
- boolean visible) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId, visible ? 1 : 0);
-
+ public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
+ // On Automotive / Headless System User Mode, the system user will be started twice:
+ // - Once by some external or local service that switches the system user to
+ // the background.
+ // - Once by the ActivityManagerService, when the system is marked ready.
+ // These two events are not synchronized and the order of execution is
+ // non-deterministic. To avoid starting the system user twice, verify whether
+ // the system user has already been started by checking the mTargetUsers.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) {
+ Slog.e(TAG, "Skipping starting system user twice");
+ return;
+ }
mTargetUsers.put(userId, targetUser);
}
-
- if (visible) {
- // Must send the user visiiblity change first, for 2 reasons:
- // 1. Automotive need to update the user-zone mapping ASAP and it's one of the few
- // services listening to this event (OTOH, there are manyy listeners to USER_STARTING
- // and some can take a while to process it)
- // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
- // called onUserSwitching(), so calling it before onUserStarting() make it more
- // consistent with that
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
- onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
- }
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
/**
- * Updates the user visibility.
- *
- * <p><b>NOTE: </b>this method should only be called when a user that is already running become
- * visible; if the user is starting visible, callers should call
- * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead.
- */
- public void onUserVisible(@UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
- onUser(USER_VISIBLE, userId);
- }
-
- /**
- * Updates the visibility of the system user.
- *
- * <p>Since the system user never stops, this method must be called when it's switched from / to
- * foreground.
- */
- public void onSystemUserVisibilityChanged(boolean visible) {
- int userId = UserHandle.USER_SYSTEM;
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
- if (visible) {
- onUser(USER_VISIBLE, userId);
- } else {
- onUser(USER_INVISIBLE, userId);
- }
- }
-
- /**
* Unlocks the given user.
*/
public void onUserUnlocking(@UserIdInt int userId) {
@@ -452,12 +419,9 @@
/**
* Stops the given user.
*/
- public void onUserStopping(@UserIdInt int userId, boolean visibilityChanged) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId, visibilityChanged ? 1 : 0);
+ public void onUserStopping(@UserIdInt int userId) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId);
onUser(USER_STOPPING, userId);
- if (visibilityChanged) {
- onUser(USER_INVISIBLE, userId);
- }
}
/**
@@ -580,12 +544,6 @@
threadPool.submit(getOnUserCompletedEventRunnable(
t, service, serviceName, curUser, completedEventType));
break;
- case USER_VISIBLE:
- service.onUserVisibilityChanged(curUser, /* visible= */ true);
- break;
- case USER_INVISIBLE:
- service.onUserVisibilityChanged(curUser, /* visible= */ false);
- break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 3f1d1fe..ae50b23 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -186,6 +186,10 @@
synchronized (mVpns) {
for (int i = 0; i < mVpns.size(); i++) {
pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
+ pw.increaseIndent();
+ mVpns.valueAt(i).dump(pw);
+ pw.decreaseIndent();
+ pw.println();
}
pw.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 672ee0e..c7c2655 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -528,7 +528,7 @@
private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
List<String> accountTypes, Integer callingUid, UserAccounts accounts) {
if (!packageExistsForUser(packageName, accounts.userId)) {
- Log.d(TAG, "Package not found " + packageName);
+ Log.w(TAG, "getAccountsAndVisibilityForPackage#Package not found " + packageName);
return new LinkedHashMap<>();
}
@@ -677,7 +677,7 @@
restoreCallingIdentity(identityToken);
}
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "resolveAccountVisibility#Package not found " + e.getMessage());
return AccountManager.VISIBILITY_NOT_VISIBLE;
}
@@ -756,7 +756,7 @@
}
return true;
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "isPreOApplication#Package not found " + e.getMessage());
return true;
}
}
@@ -840,6 +840,7 @@
}
if (notify) {
+ Log.i(TAG, "Notifying visibility changed for package=" + packageName);
for (Entry<String, Integer> packageToVisibility : packagesToVisibility
.entrySet()) {
int oldVisibility = packageToVisibility.getValue();
@@ -850,9 +851,14 @@
}
}
for (String packageNameToNotify : accountRemovedReceivers) {
- sendAccountRemovedBroadcast(account, packageNameToNotify, accounts.userId);
+ sendAccountRemovedBroadcast(
+ account,
+ packageNameToNotify,
+ accounts.userId,
+ /*useCase=*/"setAccountVisibility");
}
- sendAccountsChangedBroadcast(accounts.userId);
+ sendAccountsChangedBroadcast(
+ accounts.userId, account.type, /*useCase=*/"setAccountVisibility");
}
return true;
}
@@ -973,6 +979,8 @@
* @param accounts UserAccount that currently hosts the account
*/
private void notifyPackage(String packageName, UserAccounts accounts) {
+ Log.i(TAG, "notifying package=" + packageName + " for userId=" + accounts.userId
+ +", sending broadcast of " + AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
Intent intent = new Intent(AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
intent.setPackage(packageName);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1059,13 +1067,21 @@
|| AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE.equals(packageName));
}
- private void sendAccountsChangedBroadcast(int userId) {
- Log.i(TAG, "the accounts changed, sending broadcast of "
- + ACCOUNTS_CHANGED_INTENT.getAction());
+ private void sendAccountsChangedBroadcast(
+ int userId, String accountType, @NonNull String useCase) {
+ Objects.requireNonNull(useCase, "useCase can't be null");
+ Log.i(TAG, "the accountType= " + (accountType == null ? "" : accountType)
+ + " changed with useCase=" + useCase + " for userId=" + userId
+ + ", sending broadcast of " + ACCOUNTS_CHANGED_INTENT.getAction());
mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
}
- private void sendAccountRemovedBroadcast(Account account, String packageName, int userId) {
+ private void sendAccountRemovedBroadcast(
+ Account account, String packageName, int userId, @NonNull String useCase) {
+ Objects.requireNonNull(useCase, "useCase can't be null");
+ Log.i(TAG, "the account with type=" + account.type + " removed while useCase="
+ + useCase + " for userId=" + userId + ", sending broadcast of "
+ + AccountManager.ACTION_ACCOUNT_REMOVED);
Intent intent = new Intent(AccountManager.ACTION_ACCOUNT_REMOVED);
intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.setPackage(packageName);
@@ -1212,6 +1228,8 @@
accountsDb.endTransaction();
}
accountDeleted = true;
+ Log.i(TAG, "validateAccountsInternal#Deleted UserId="
+ + accounts.userId + ", AccountId=" + accountId);
logRecord(AccountsDb.DEBUG_ACTION_AUTHENTICATOR_REMOVE,
AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
@@ -1228,7 +1246,11 @@
}
}
for (String packageName : accountRemovedReceivers) {
- sendAccountRemovedBroadcast(account, packageName, accounts.userId);
+ sendAccountRemovedBroadcast(
+ account,
+ packageName,
+ accounts.userId,
+ /*useCase=*/"validateAccounts");
}
} else {
ArrayList<String> accountNames = accountNamesByType.get(account.type);
@@ -1253,7 +1275,10 @@
AccountManager.invalidateLocalAccountsDataCaches();
} finally {
if (accountDeleted) {
- sendAccountsChangedBroadcast(accounts.userId);
+ sendAccountsChangedBroadcast(
+ accounts.userId,
+ /*accountType=*/"ambiguous",
+ /*useCase=*/"validateAccounts");
}
}
}
@@ -1845,7 +1870,7 @@
}
if (accounts.accountsDb.findAllDeAccounts().size() > 100) {
Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString()
- + ", skipping since more than 50 accounts on device exist");
+ + ", skipping since more than 100 accounts on device exist");
return false;
}
long accountId = accounts.accountsDb.insertCeAccount(account, password);
@@ -1899,7 +1924,9 @@
sendNotificationAccountUpdated(account, accounts);
// Only send LOGIN_ACCOUNTS_CHANGED when the database changed.
- sendAccountsChangedBroadcast(accounts.userId);
+ Log.i(TAG, "callingUid=" + callingUid + ", userId=" + accounts.userId
+ + " added account");
+ sendAccountsChangedBroadcast(accounts.userId, account.type, /*useCase=*/"addAccount");
logAddAccountExplicitlyMetrics(opPackageName, account.type, packageToVisibility);
return true;
@@ -2089,6 +2116,8 @@
final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
+ Log.i(TAG, "callingUid=" + callingUid + ", userId=" + accounts.userId
+ + " performing rename account");
Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName);
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name);
@@ -2199,9 +2228,14 @@
}
sendNotificationAccountUpdated(resultAccount, accounts);
- sendAccountsChangedBroadcast(accounts.userId);
+ sendAccountsChangedBroadcast(
+ accounts.userId, accountToRename.type, /*useCase=*/"renameAccount");
for (String packageName : accountRemovedReceivers) {
- sendAccountRemovedBroadcast(accountToRename, packageName, accounts.userId);
+ sendAccountRemovedBroadcast(
+ accountToRename,
+ packageName,
+ accounts.userId,
+ /*useCase=*/"renameAccount");
}
AccountManager.invalidateLocalAccountsDataCaches();
@@ -2433,9 +2467,13 @@
}
// Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
- sendAccountsChangedBroadcast(accounts.userId);
+ Log.i(TAG, "callingUid=" + callingUid + ", userId=" + accounts.userId
+ + " removed account");
+ sendAccountsChangedBroadcast(
+ accounts.userId, account.type, /*useCase=*/"removeAccount");
for (String packageName : accountRemovedReceivers) {
- sendAccountRemovedBroadcast(account, packageName, accounts.userId);
+ sendAccountRemovedBroadcast(
+ account, packageName, accounts.userId, /*useCase=*/"removeAccount");
}
String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
: AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
@@ -2709,7 +2747,9 @@
if (isChanged) {
// Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
sendNotificationAccountUpdated(account, accounts);
- sendAccountsChangedBroadcast(accounts.userId);
+ Log.i(TAG, "callingUid=" + callingUid + " changed password");
+ sendAccountsChangedBroadcast(
+ accounts.userId, account.type, /*useCase=*/"setPassword");
}
}
}
@@ -3480,10 +3520,10 @@
@Override
protected String toDebugString(long now) {
- String requiredFeaturesStr = TextUtils.join(",", requiredFeatures);
return super.toDebugString(now) + ", startAddAccountSession" + ", accountType "
+ accountType + ", requiredFeatures "
- + (requiredFeatures != null ? requiredFeaturesStr : null);
+ + (requiredFeatures != null
+ ? TextUtils.join(",", requiredFeatures) : "null");
}
}.bind();
} finally {
@@ -4063,7 +4103,7 @@
int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
return hasAccountAccess(account, packageName, uid);
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "hasAccountAccess#Package not found " + e.getMessage());
return false;
}
}
@@ -4195,7 +4235,7 @@
}
final long token = Binder.clearCallingIdentity();
try {
- AccountAndUser[] allAccounts = getAllAccounts();
+ AccountAndUser[] allAccounts = getAllAccountsForSystemProcess();
for (int i = allAccounts.length - 1; i >= 0; i--) {
if (allAccounts[i].account.equals(account)) {
return true;
@@ -4345,10 +4385,11 @@
/**
* Returns accounts for all running users, ignores visibility values.
*
+ * Should only be called by System process.
* @hide
*/
@NonNull
- public AccountAndUser[] getRunningAccounts() {
+ public AccountAndUser[] getRunningAccountsForSystem() {
final int[] runningUserIds;
try {
runningUserIds = ActivityManager.getService().getRunningUserIds();
@@ -4356,26 +4397,34 @@
// Running in system_server; should never happen
throw new RuntimeException(e);
}
- return getAccounts(runningUserIds);
+ return getAccountsForSystem(runningUserIds);
}
/**
* Returns accounts for all users, ignores visibility values.
*
+ * Should only be called by system process
+ *
* @hide
*/
@NonNull
- public AccountAndUser[] getAllAccounts() {
+ public AccountAndUser[] getAllAccountsForSystemProcess() {
final List<UserInfo> users = getUserManager().getAliveUsers();
final int[] userIds = new int[users.size()];
for (int i = 0; i < userIds.length; i++) {
userIds[i] = users.get(i).id;
}
- return getAccounts(userIds);
+ return getAccountsForSystem(userIds);
}
+ /**
+ * Returns all accounts for the given user, ignores all visibility checks.
+ * This should only be called by system process.
+ *
+ * @hide
+ */
@NonNull
- private AccountAndUser[] getAccounts(int[] userIds) {
+ private AccountAndUser[] getAccountsForSystem(int[] userIds) {
final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
for (int userId : userIds) {
UserAccounts userAccounts = getUserAccounts(userId);
@@ -4384,7 +4433,7 @@
userAccounts,
null /* type */,
Binder.getCallingUid(),
- null /* packageName */,
+ "android"/* packageName */,
false /* include managed not visible*/);
for (Account account : accounts) {
runningAccounts.add(new AccountAndUser(account, userId));
@@ -4795,6 +4844,7 @@
private abstract class Session extends IAccountAuthenticatorResponse.Stub
implements IBinder.DeathRecipient, ServiceConnection {
+ private final Object mSessionLock = new Object();
IAccountManagerResponse mResponse;
final String mAccountType;
final boolean mExpectActivityLaunch;
@@ -4985,9 +5035,11 @@
}
private void unbind() {
- if (mAuthenticator != null) {
- mAuthenticator = null;
- mContext.unbindService(this);
+ synchronized (mSessionLock) {
+ if (mAuthenticator != null) {
+ mAuthenticator = null;
+ mContext.unbindService(this);
+ }
}
}
@@ -4997,12 +5049,14 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
- try {
- run();
- } catch (RemoteException e) {
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
- "remote exception");
+ synchronized (mSessionLock) {
+ mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
+ try {
+ run();
+ } catch (RemoteException e) {
+ onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+ "remote exception");
+ }
}
}
@@ -5355,7 +5409,7 @@
}
} else {
Account[] accounts = getAccountsFromCache(userAccounts, null /* type */,
- Process.SYSTEM_UID, null /* packageName */, false);
+ Process.SYSTEM_UID, "android" /* packageName */, false);
fout.println("Accounts: " + accounts.length);
for (Account account : accounts) {
fout.println(" " + account.toString());
@@ -5550,7 +5604,7 @@
return true;
}
} catch (PackageManager.NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "isPrivileged#Package not found " + e.getMessage());
}
}
} finally {
@@ -6074,7 +6128,7 @@
}
}
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "filterSharedAccounts#Package not found " + e.getMessage());
}
Map<Account, Integer> filtered = new LinkedHashMap<>();
for (Map.Entry<Account, Integer> entry : unfiltered.entrySet()) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d0f245f..6bb3306 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -23,18 +23,29 @@
import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_CARRIER_PRIVILEGED_APP;
import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
import static android.os.PowerExemptionManager.REASON_CURRENT_INPUT_METHOD;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
import static android.os.PowerExemptionManager.REASON_DEVICE_OWNER;
+import static android.os.PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL;
+import static android.os.PowerExemptionManager.REASON_DPO_PROTECTED_APP;
import static android.os.PowerExemptionManager.REASON_FGS_BINDING;
import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION;
@@ -45,10 +56,12 @@
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
+import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
import static android.os.PowerExemptionManager.REASON_SERVICE_LAUNCH;
import static android.os.PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
import static android.os.PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
@@ -63,6 +76,9 @@
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT;
@@ -94,6 +110,11 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.ForegroundServiceStartNotAllowedException;
+import android.app.ForegroundServiceTypeNotAllowedException;
+import android.app.ForegroundServiceTypePolicy;
+import android.app.ForegroundServiceTypePolicy.ForegroundServicePolicyCheckCode;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePermission;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePolicyInfo;
import android.app.IApplicationThread;
import android.app.IForegroundServiceObserver;
import android.app.IServiceConnection;
@@ -116,13 +137,14 @@
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.net.Uri;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -163,7 +185,6 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.ServiceState;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
@@ -206,8 +227,6 @@
private static final boolean LOG_SERVICE_START_STOP = false;
- private static final boolean SHOW_DUNGEON_NOTIFICATION = false;
-
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -287,6 +306,12 @@
final ArrayList<ServiceRecord> mPendingFgsNotifications = new ArrayList<>();
/**
+ * Map of ForegroundServiceDelegation to the delegation ServiceRecord. The delegation
+ * ServiceRecord has flag isFgsDelegate set to true.
+ */
+ final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
+
+ /**
* Whether there is a rate limit that suppresses immediate re-deferral of new FGS
* notifications from each app. On by default, disabled only by shell command for
* test-suite purposes. To disable the behavior more generally, use the usual
@@ -539,7 +564,7 @@
try {
final ServiceRecord.StartItem si = r.pendingStarts.get(0);
startServiceInnerLocked(this, si.intent, r, false, true, si.callingId,
- r.startRequested);
+ si.mCallingProcessName, r.startRequested);
} catch (TransactionTooLargeException e) {
// Ignore, nobody upstack cares.
}
@@ -576,6 +601,7 @@
getAppStateTracker().addBackgroundRestrictedAppListener(new BackgroundRestrictedListener());
mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class);
setAllowListWhileInUsePermissionInFgs();
+ initSystemExemptedFgsTypePermission();
}
private AppStateTracker getAppStateTracker() {
@@ -630,25 +656,6 @@
return false;
}
- void stopForegroundServicesForChannelLocked(String pkg, int userId, String channelId) {
- final ServiceMap smap = mServiceMap.get(userId);
- if (smap != null) {
- for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
- final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
- if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
- if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) {
- if (DEBUG_FOREGROUND_SERVICE) {
- Slog.d(TAG_SERVICE, "Stopping FGS u" + userId + "/pkg=" + pkg
- + "/channelId=" + channelId
- + " for conversation channel clear");
- }
- stopServiceLocked(sr, false);
- }
- }
- }
- }
- }
-
private ServiceMap getServiceMapLocked(int callingUser) {
ServiceMap smap = mServiceMap.get(callingUser);
if (smap == null) {
@@ -724,7 +731,7 @@
ServiceRecord r = res.record;
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
- allowBackgroundActivityStarts);
+ allowBackgroundActivityStarts, false /* isBindService */);
if (!mAm.mUserController.exists(r.userId)) {
Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -757,8 +764,8 @@
Slog.w(TAG, msg);
showFgsBgRestrictedNotificationLocked(r);
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
- 0, FGS_STOP_REASON_UNKNOWN);
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
throw new ForegroundServiceStartNotAllowedException(msg);
}
@@ -861,8 +868,10 @@
// alias component name to the client, not the "target" component name, which is
// what realResult contains.
final ComponentName realResult =
- startServiceInnerLocked(r, service, callingUid, callingPid, fgRequired, callerFg,
- allowBackgroundActivityStarts, backgroundActivityStartsToken);
+ startServiceInnerLocked(r, service, callingUid, callingPid,
+ getCallingProcessNameLocked(callingUid, callingPid, callingPackage),
+ fgRequired, callerFg, allowBackgroundActivityStarts,
+ backgroundActivityStartsToken);
if (res.aliasComponent != null
&& !realResult.getPackageName().startsWith("!")
&& !realResult.getPackageName().startsWith("?")) {
@@ -872,10 +881,18 @@
}
}
+ private String getCallingProcessNameLocked(int callingUid, int callingPid,
+ String callingPackage) {
+ synchronized (mAm.mPidsSelfLocked) {
+ final ProcessRecord callingApp = mAm.mPidsSelfLocked.get(callingPid);
+ return callingApp != null ? callingApp.processName : callingPackage;
+ }
+ }
+
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
- int callingUid, int callingPid, boolean fgRequired, boolean callerFg,
- boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken)
- throws TransactionTooLargeException {
+ int callingUid, int callingPid, String callingProcessName, boolean fgRequired,
+ boolean callerFg, boolean allowBackgroundActivityStarts,
+ @Nullable IBinder backgroundActivityStartsToken) throws TransactionTooLargeException {
NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
service, callingUid, r.packageName, r.userId);
if (unscheduleServiceRestartLocked(r, callingUid, false)) {
@@ -887,7 +904,7 @@
r.delayedStop = false;
r.fgRequired = fgRequired;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- service, neededGrants, callingUid));
+ service, neededGrants, callingUid, callingProcessName));
if (fgRequired) {
// We are now effectively running a foreground service.
@@ -972,7 +989,7 @@
r.allowBgActivityStartsOnServiceStart(backgroundActivityStartsToken);
}
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting,
- callingUid, wasStartRequested);
+ callingUid, callingProcessName, wasStartRequested);
return cmp;
}
@@ -1090,6 +1107,8 @@
curPendingBringups = new ArrayList<>();
mPendingBringups.put(s, curPendingBringups);
}
+ final String callingProcessName = getCallingProcessNameLocked(
+ callingUid, callingPid, callingPackage);
curPendingBringups.add(new Runnable() {
@Override
public void run() {
@@ -1122,8 +1141,8 @@
} else { // Starting a service
try {
startServiceInnerLocked(s, serviceIntent, callingUid, callingPid,
- fgRequired, callerFg, allowBackgroundActivityStarts,
- backgroundActivityStartsToken);
+ callingProcessName, fgRequired, callerFg,
+ allowBackgroundActivityStarts, backgroundActivityStartsToken);
} catch (TransactionTooLargeException e) {
/* ignore - local call */
}
@@ -1168,8 +1187,8 @@
}
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
- boolean callerFg, boolean addToStarting, int callingUid, boolean wasStartRequested)
- throws TransactionTooLargeException {
+ boolean callerFg, boolean addToStarting, int callingUid, String callingProcessName,
+ boolean wasStartRequested) throws TransactionTooLargeException {
synchronized (mAm.mProcessStats.mLock) {
final ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1203,7 +1222,8 @@
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
: (wasStartRequested || !r.getConnections().isEmpty()
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT
- : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM));
+ : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
+ getShortProcessNameForStats(callingUid, callingProcessName));
if (r.startRequested && addToStarting) {
boolean first = smap.mStartingBackground.size() == 0;
@@ -1226,6 +1246,22 @@
return r.name;
}
+ private @Nullable String getShortProcessNameForStats(int uid, String processName) {
+ final String[] packages = mAm.mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length == 1) {
+ // Not the shared UID case, let's see if the package name equals to the process name.
+ if (TextUtils.equals(packages[0], processName)) {
+ // same name, just return null here.
+ return null;
+ } else if (processName != null && processName.startsWith(packages[0])) {
+ // return the suffix of the process name
+ return processName.substring(packages[0].length());
+ }
+ }
+ // return the full process name.
+ return processName;
+ }
+
private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) {
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopServiceLocked()");
@@ -1530,9 +1566,11 @@
return canRemove;
}
+ /**
+ * Stop FGSs owned by non-top, BG-restricted apps.
+ */
void updateForegroundApps(ServiceMap smap) {
// This is called from the handler without the lock held.
- ArrayList<ActiveForegroundApp> active = null;
synchronized (mAm) {
final long now = SystemClock.elapsedRealtime();
long nextUpdateTime = Long.MAX_VALUE;
@@ -1558,12 +1596,8 @@
// it loses the fg service state now.
if (isForegroundServiceAllowedInBackgroundRestricted(
aa.mUid, aa.mPackageName)) {
- if (active == null) {
- active = new ArrayList<>();
- }
if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Adding active: pkg="
+ aa.mPackageName + ", uid=" + aa.mUid);
- active.add(aa);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "bg-restricted app "
@@ -1583,89 +1617,8 @@
+ SystemClock.uptimeMillis() - SystemClock.elapsedRealtime());
}
}
- if (!smap.mActiveForegroundAppsChanged) {
- return;
- }
smap.mActiveForegroundAppsChanged = false;
}
-
- if (!SHOW_DUNGEON_NOTIFICATION) {
- return;
- }
-
- final NotificationManager nm = (NotificationManager) mAm.mContext.getSystemService(
- Context.NOTIFICATION_SERVICE);
- final Context context = mAm.mContext;
-
- if (active != null) {
- for (int i = 0; i < active.size(); i++) {
- ActiveForegroundApp aa = active.get(i);
- if (aa.mLabel == null) {
- PackageManager pm = context.getPackageManager();
- try {
- ApplicationInfo ai = pm.getApplicationInfoAsUser(aa.mPackageName,
- PackageManager.MATCH_KNOWN_PACKAGES, smap.mUserId);
- aa.mLabel = ai.loadLabel(pm);
- } catch (PackageManager.NameNotFoundException e) {
- aa.mLabel = aa.mPackageName;
- }
- }
- }
-
- Intent intent;
- String title;
- String msg;
- String[] pkgs;
- final long nowElapsed = SystemClock.elapsedRealtime();
- long oldestStartTime = nowElapsed;
- if (active.size() == 1) {
- intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- intent.setData(Uri.fromParts("package", active.get(0).mPackageName, null));
- title = context.getString(
- R.string.foreground_service_app_in_background, active.get(0).mLabel);
- msg = context.getString(R.string.foreground_service_tap_for_details);
- pkgs = new String[] { active.get(0).mPackageName };
- oldestStartTime = active.get(0).mStartTime;
- } else {
- intent = new Intent(Settings.ACTION_FOREGROUND_SERVICES_SETTINGS);
- pkgs = new String[active.size()];
- for (int i = 0; i < active.size(); i++) {
- pkgs[i] = active.get(i).mPackageName;
- oldestStartTime = Math.min(oldestStartTime, active.get(i).mStartTime);
- }
- intent.putExtra("packages", pkgs);
- title = context.getString(
- R.string.foreground_service_apps_in_background, active.size());
- msg = active.get(0).mLabel.toString();
- for (int i = 1; i < active.size(); i++) {
- msg = context.getString(R.string.foreground_service_multiple_separator,
- msg, active.get(i).mLabel);
- }
- }
- Bundle notificationBundle = new Bundle();
- notificationBundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
- Notification.Builder n =
- new Notification.Builder(context,
- SystemNotificationChannels.FOREGROUND_SERVICE)
- .addExtras(notificationBundle)
- .setSmallIcon(R.drawable.stat_sys_vitals)
- .setOngoing(true)
- .setShowWhen(oldestStartTime < nowElapsed)
- .setWhen(System.currentTimeMillis() - (nowElapsed - oldestStartTime))
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setContentTitle(title)
- .setContentText(msg)
- .setContentIntent(
- PendingIntent.getActivityAsUser(context, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
- null, new UserHandle(smap.mUserId)));
- nm.notifyAsUser(null, SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
- n.build(), new UserHandle(smap.mUserId));
- } else {
- nm.cancelAsUser(null, SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
- new UserHandle(smap.mUserId));
- }
}
private void requestUpdateActiveForegroundAppsLocked(ServiceMap smap, long timeElapsed) {
@@ -1911,6 +1864,7 @@
ignoreForeground = true;
}
+ int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
if (!ignoreForeground) {
if (r.mStartForegroundCount == 0) {
/*
@@ -1931,7 +1885,9 @@
if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
resetFgsRestrictionLocked(r);
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+ false /* allowBackgroundActivityStarts */,
+ false /* isBindService */);
final String temp = "startForegroundDelayMs:" + delayMs;
if (r.mInfoAllowStartForeground != null) {
r.mInfoAllowStartForeground += "; " + temp;
@@ -1945,7 +1901,9 @@
// The second or later time startForeground() is called after service is
// started. Check for app state again.
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+ false /* allowBackgroundActivityStarts */,
+ false /* isBindService */);
}
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
@@ -1965,13 +1923,49 @@
updateServiceForegroundLocked(psr, true);
ignoreForeground = true;
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
- 0, FGS_STOP_REASON_UNKNOWN);
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
r.appInfo.uid)) {
throw new ForegroundServiceStartNotAllowedException(msg);
}
}
+
+ if (!ignoreForeground) {
+ Pair<Integer, RuntimeException> fgsTypeResult = null;
+ if (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ fgsTypeResult = validateForegroundServiceType(r,
+ foregroundServiceType,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+ } else {
+ int fgsTypes = foregroundServiceType;
+ // If the service has declared some unknown types which might be coming
+ // from future releases, and if it also comes with the "specialUse",
+ // then it'll be deemed as the "specialUse" and we ignore this
+ // unknown type. Otherwise, it'll be treated as an invalid type.
+ int defaultFgsTypes = (foregroundServiceType
+ & ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE) != 0
+ ? ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ : ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+ for (int serviceType = Integer.highestOneBit(fgsTypes);
+ serviceType != 0;
+ serviceType = Integer.highestOneBit(fgsTypes)) {
+ fgsTypeResult = validateForegroundServiceType(r,
+ serviceType, defaultFgsTypes);
+ fgsTypes &= ~serviceType;
+ if (fgsTypeResult.first != FGS_TYPE_POLICY_CHECK_OK) {
+ break;
+ }
+ }
+ }
+ fgsTypeCheckCode = fgsTypeResult.first;
+ if (fgsTypeResult.second != null) {
+ logFGSStateChangeLocked(r,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+ 0, FGS_STOP_REASON_UNKNOWN, fgsTypeResult.first);
+ throw fgsTypeResult.second;
+ }
+ }
}
// Apps under strict background restrictions simply don't get to have foreground
@@ -2040,8 +2034,8 @@
registerAppOpCallbackLocked(r);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
- 0, FGS_STOP_REASON_UNKNOWN);
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+ 0, FGS_STOP_REASON_UNKNOWN, fgsTypeCheckCode);
updateNumForegroundServicesLocked();
}
// Even if the service is already a FGS, we need to update the notification,
@@ -2122,10 +2116,11 @@
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
unregisterAppOpCallbackLocked(r);
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
r.mFgsExitTime > r.mFgsEnterTime
? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
- FGS_STOP_REASON_STOP_FOREGROUND);
+ FGS_STOP_REASON_STOP_FOREGROUND,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN);
r.mFgsNotificationWasDeferred = false;
signalForegroundServiceObserversLocked(r);
resetFgsRestrictionLocked(r);
@@ -2161,6 +2156,118 @@
return now < eligible;
}
+ /**
+ * Validate if the given service can start a foreground service with given type.
+ *
+ * @return A pair, where the first parameter is the result code and second is the exception
+ * object if it fails to start a foreground service with given type.
+ */
+ @NonNull
+ private Pair<Integer, RuntimeException> validateForegroundServiceType(ServiceRecord r,
+ @ForegroundServiceType int type,
+ @ForegroundServiceType int defaultToType) {
+ final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+ final ForegroundServiceTypePolicyInfo policyInfo =
+ policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
+ final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
+ mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
+ r.mAllowWhileInUsePermissionInFgs, policyInfo);
+ RuntimeException exception = null;
+ switch (code) {
+ case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
+ final String msg = "Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " code=" + code
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion;
+ Slog.wtfQuiet(TAG, msg);
+ Slog.w(TAG, msg);
+ } break;
+ case FGS_TYPE_POLICY_CHECK_DISABLED: {
+ exception = new ForegroundServiceTypeNotAllowedException(
+ "Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion
+ + " has been prohibited");
+ } break;
+ case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE: {
+ final String msg = "Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " code=" + code
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion
+ + " requiredPermissions=" + policyInfo.toPermissionString();
+ Slog.wtfQuiet(TAG, msg);
+ Slog.w(TAG, msg);
+ } break;
+ case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED: {
+ exception = new SecurityException("Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion
+ + " requires permissions: "
+ + policyInfo.toPermissionString());
+ } break;
+ case FGS_TYPE_POLICY_CHECK_OK:
+ default:
+ break;
+ }
+ return Pair.create(code, exception);
+ }
+
+ private class SystemExemptedFgsTypePermission extends ForegroundServiceTypePermission {
+ SystemExemptedFgsTypePermission() {
+ super("System exempted");
+ }
+
+ @Override
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+ @NonNull String packageName, boolean allowWhileInUse) {
+ final AppRestrictionController appRestrictionController = mAm.mAppRestrictionController;
+ @ReasonCode int reason = appRestrictionController
+ .getPotentialSystemExemptionReason(callerUid);
+ if (reason == REASON_DENIED) {
+ reason = appRestrictionController
+ .getPotentialSystemExemptionReason(callerUid, packageName);
+ if (reason == REASON_DENIED) {
+ reason = appRestrictionController
+ .getPotentialUserAllowedExemptionReason(callerUid, packageName);
+ }
+ }
+ switch (reason) {
+ case REASON_SYSTEM_UID:
+ case REASON_SYSTEM_ALLOW_LISTED:
+ case REASON_DEVICE_DEMO_MODE:
+ case REASON_DISALLOW_APPS_CONTROL:
+ case REASON_DEVICE_OWNER:
+ case REASON_PROFILE_OWNER:
+ case REASON_PROC_STATE_PERSISTENT:
+ case REASON_PROC_STATE_PERSISTENT_UI:
+ case REASON_SYSTEM_MODULE:
+ case REASON_CARRIER_PRIVILEGED_APP:
+ case REASON_DPO_PROTECTED_APP:
+ case REASON_ACTIVE_DEVICE_ADMIN:
+ case REASON_ROLE_EMERGENCY:
+ case REASON_ALLOWLISTED_PACKAGE:
+ return PERMISSION_GRANTED;
+ default:
+ return PERMISSION_DENIED;
+ }
+ }
+ }
+
+ private void initSystemExemptedFgsTypePermission() {
+ final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+ final ForegroundServiceTypePolicyInfo policyInfo =
+ policy.getForegroundServiceTypePolicyInfo(
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+ if (policyInfo != null) {
+ policyInfo.setCustomPermission(new SystemExemptedFgsTypePermission());
+ }
+ }
+
ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification,
final String tag, final int id, final String pkg, final int userId) {
// By nature of the FGS API, all FGS notifications have a null tag
@@ -2866,7 +2973,7 @@
ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
- isBindExternal, allowInstant);
+ isBindExternal, allowInstant, null /* fgsDelegateOptions */);
if (res == null) {
return 0;
}
@@ -2985,7 +3092,7 @@
}
}
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
- false);
+ false /* allowBackgroundActivityStarts */, true /* isBindService */);
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
@@ -3015,7 +3122,8 @@
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
: (wasStartRequested || hadConnections
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT
- : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM));
+ : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
+ getShortProcessNameForStats(callingUid, callerApp.processName));
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
+ ": received=" + b.intent.received
@@ -3324,7 +3432,7 @@
boolean allowInstant) {
return retrieveServiceLocked(service, instanceName, false, 0, null, resolvedType,
callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
- isBindExternal, allowInstant);
+ isBindExternal, allowInstant, null /* fgsDelegateOptions */);
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -3332,7 +3440,7 @@
String sdkSandboxClientAppPackage, String resolvedType,
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
- boolean allowInstant) {
+ boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions) {
if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
@@ -3395,6 +3503,53 @@
}
}
}
+
+ if (r == null && fgsDelegateOptions != null) {
+ // Create a ServiceRecord for FGS delegate.
+ final ServiceInfo sInfo = new ServiceInfo();
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+ fgsDelegateOptions.mClientPackageName,
+ ActivityManagerService.STOCK_PM_FLAGS,
+ userId);
+ } catch (RemoteException ex) {
+ // pm is in same process, this will never happen.
+ }
+ if (aInfo == null) {
+ throw new SecurityException("startForegroundServiceDelegate failed, "
+ + "could not resolve client package " + callingPackage);
+ }
+ if (aInfo.uid != fgsDelegateOptions.mClientUid) {
+ throw new SecurityException("startForegroundServiceDelegate failed, "
+ + "uid:" + aInfo.uid
+ + " does not match clientUid:" + fgsDelegateOptions.mClientUid);
+ }
+ sInfo.applicationInfo = aInfo;
+ sInfo.packageName = aInfo.packageName;
+ sInfo.mForegroundServiceType = fgsDelegateOptions.mForegroundServiceTypes;
+ sInfo.processName = aInfo.processName;
+ final ComponentName cn = service.getComponent();
+ sInfo.name = cn.getClassName();
+ if (createIfNeeded) {
+ final Intent.FilterComparison filter =
+ new Intent.FilterComparison(service.cloneFilter());
+ final ServiceRestarter res = new ServiceRestarter();
+ r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
+ sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
+ callingFromFg, res, null /* sdkSandboxProcessName */,
+ INVALID_UID /* sdkSandboxClientAppUid */,
+ null /* sdkSandboxClientAppPackage */);
+ res.setService(r);
+ smap.mServicesByInstanceName.put(cn, r);
+ smap.mServicesByIntent.put(filter, r);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Retrieve created new service: " + r);
+ r.mRecentCallingPackage = callingPackage;
+ r.mRecentCallingUid = callingUid;
+ }
+ return new ServiceLookupResult(r, resolution.getAlias());
+ }
+
if (r == null) {
try {
int flags = ActivityManagerService.STOCK_PM_FLAGS
@@ -4484,7 +4639,7 @@
// be called.
if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- null, null, 0));
+ null, null, 0, null));
}
sendServiceArgsLocked(r, execInFg, true);
@@ -4773,10 +4928,11 @@
unregisterAppOpCallbackLocked(r);
r.mFgsExitTime = SystemClock.uptimeMillis();
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
r.mFgsExitTime > r.mFgsEnterTime
? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
- FGS_STOP_REASON_STOP_SERVICE);
+ FGS_STOP_REASON_STOP_SERVICE,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
}
@@ -4805,16 +4961,31 @@
// Bump the process to the top of LRU list
mAm.updateLruProcessLocked(r.app, false, null);
updateServiceForegroundLocked(r.app.mServices, false);
- try {
- oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
- mDestroyingServices.add(r);
- r.destroying = true;
- r.app.getThread().scheduleStopService(r);
- } catch (Exception e) {
- Slog.w(TAG, "Exception when destroying service "
- + r.shortInstanceName, e);
- serviceProcessGoneLocked(r, enqueueOomAdj);
+ if (r.mIsFgsDelegate) {
+ if (r.mFgsDelegation.mConnection != null) {
+ mAm.mHandler.post(() -> {
+ r.mFgsDelegation.mConnection.onServiceDisconnected(
+ r.mFgsDelegation.mOptions.getComponentName());
+ });
+ }
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ if (mFgsDelegations.valueAt(i) == r) {
+ mFgsDelegations.removeAt(i);
+ break;
+ }
+ }
+ } else {
+ try {
+ oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
+ oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mDestroyingServices.add(r);
+ r.destroying = true;
+ r.app.getThread().scheduleStopService(r);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception when destroying service "
+ + r.shortInstanceName, e);
+ serviceProcessGoneLocked(r, enqueueOomAdj);
+ }
}
} else {
if (DEBUG_SERVICE) Slog.v(
@@ -5442,7 +5613,7 @@
stopServiceLocked(sr, true);
} else {
sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
- sr.getLastStartId(), baseIntent, null, 0));
+ sr.getLastStartId(), baseIntent, null, 0, null));
if (sr.app != null && sr.app.getThread() != null) {
// We always run in the foreground, since this is called as
// part of the "remove task" UI operation.
@@ -6500,7 +6671,7 @@
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- boolean allowBackgroundActivityStarts) {
+ boolean allowBackgroundActivityStarts, boolean isBindService) {
r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
// Check DeviceConfig flag.
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
@@ -6510,14 +6681,15 @@
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
- callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts);
+ callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts,
+ isBindService);
if (!r.mAllowWhileInUsePermissionInFgs) {
r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
}
if (r.mAllowStartForeground == REASON_DENIED) {
r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
- userId);
+ userId, isBindService);
}
}
}
@@ -6537,9 +6709,10 @@
}
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, null /* serviceRecord */,
- false /* allowBackgroundActivityStarts */);
+ false /* allowBackgroundActivityStarts */, false);
@ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
- allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */);
+ allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
+ false /* isBindService */);
if (allowStartFgs == REASON_DENIED) {
if (canBindingClientStartFgsLocked(callingUid) != null) {
@@ -6559,7 +6732,7 @@
*/
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
int callingPid, int callingUid, @Nullable ServiceRecord targetService,
- boolean allowBackgroundActivityStarts) {
+ boolean allowBackgroundActivityStarts, boolean isBindService) {
int ret = REASON_DENIED;
final int uidState = mAm.getUidStateLocked(callingUid);
@@ -6713,12 +6886,12 @@
shouldAllowFgsWhileInUsePermissionLocked(
clientPackageName,
clientPid, clientUid, null /* serviceRecord */,
- false /* allowBackgroundActivityStarts */);
+ false /* allowBackgroundActivityStarts */, false);
final @ReasonCode int allowStartFgs =
shouldAllowFgsStartForegroundNoBindingCheckLocked(
allowWhileInUse2,
clientPid, clientUid, clientPackageName,
- null /* targetService */);
+ null /* targetService */, false);
if (allowStartFgs != REASON_DENIED) {
return new Pair<>(allowStartFgs, clientPackageName);
} else {
@@ -6750,11 +6923,11 @@
*/
private @ReasonCode int shouldAllowFgsStartForegroundWithBindingCheckLocked(
@ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
- int callingUid, Intent intent, ServiceRecord r, int userId) {
+ int callingUid, Intent intent, ServiceRecord r, int userId, boolean isBindService) {
ActivityManagerService.FgsTempAllowListItem tempAllowListReason =
r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
int ret = shouldAllowFgsStartForegroundNoBindingCheckLocked(allowWhileInUse, callingPid,
- callingUid, callingPackage, r);
+ callingUid, callingPackage, r, isBindService);
String bindFromPackage = null;
if (ret == REASON_DENIED) {
@@ -6789,6 +6962,7 @@
+ "; callerTargetSdkVersion:" + callerTargetSdkVersion
+ "; startForegroundCount:" + r.mStartForegroundCount
+ "; bindFromPackage:" + bindFromPackage
+ + ": isBindService:" + isBindService
+ "]";
if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
r.mLoggedInfoAllowStartForeground = false;
@@ -6799,7 +6973,7 @@
private @ReasonCode int shouldAllowFgsStartForegroundNoBindingCheckLocked(
@ReasonCode int allowWhileInUse, int callingPid, int callingUid, String callingPackage,
- @Nullable ServiceRecord targetService) {
+ @Nullable ServiceRecord targetService, boolean isBindService) {
int ret = allowWhileInUse;
if (ret == REASON_DENIED) {
@@ -6981,10 +7155,12 @@
}
private void logFgsBackgroundStart(ServiceRecord r) {
+ /*
// Only log if FGS is started from background.
if (!isFgsBgStart(r.mAllowStartForeground)) {
return;
}
+ */
if (!r.mLoggedInfoAllowStartForeground) {
final String msg = "Background started FGS: "
+ ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
@@ -6996,10 +7172,10 @@
}
Slog.i(TAG, msg);
} else {
- if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
- mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
+ //if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
+ // mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
Slog.wtfQuiet(TAG, msg);
- }
+ //}
Slog.w(TAG, msg);
}
r.mLoggedInfoAllowStartForeground = true;
@@ -7011,17 +7187,20 @@
* @param r ServiceRecord
* @param state one of ENTER/EXIT/DENIED event.
* @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state.
+ * @param fgsStopReason why was this FGS stopped.
+ * @param fgsTypeCheckCode The FGS type policy check result.
*/
private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs,
- @FgsStopReason int fgsStopReason) {
+ @FgsStopReason int fgsStopReason,
+ @ForegroundServicePolicyCheckCode int fgsTypeCheckCode) {
if (!ActivityManagerUtils.shouldSamplePackageForAtom(
r.packageName, mAm.mConstants.mFgsAtomSampleRate)) {
return;
}
boolean allowWhileInUsePermissionInFgs;
@PowerExemptionManager.ReasonCode int fgsStartReasonCode;
- if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
- || state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+ if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
+ || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
@@ -7047,14 +7226,20 @@
r.mStartForegroundCount,
ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
r.mFgsHasNotificationPermission,
- r.foregroundServiceType);
+ r.foregroundServiceType,
+ fgsTypeCheckCode,
+ r.mIsFgsDelegate,
+ r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID,
+ r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
+ : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT
+ );
int event = 0;
- if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
+ if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
event = EventLogTags.AM_FOREGROUND_SERVICE_START;
- } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+ } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
event = EventLogTags.AM_FOREGROUND_SERVICE_STOP;
- } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
+ } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
} else {
// Unknown event.
@@ -7082,7 +7267,7 @@
String callingPackage) {
return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
/* targetService */ null,
- /* allowBackgroundActivityStarts */ false)
+ /* allowBackgroundActivityStarts */ false, false)
!= REASON_DENIED;
}
@@ -7111,4 +7296,163 @@
return "UNKNOWN";
}
}
+
+ /**
+ * Start a foreground service delegate. The delegate is not an actual service component, it is
+ * merely a delegate that promotes the client process into foreground service process state.
+ *
+ * @param options an ForegroundServiceDelegationOptions object.
+ * @param connection callback if the delegate is started successfully.
+ * @return true if delegate is started, false otherwise.
+ * @throw SecurityException if PackageManaager can not resolve
+ * {@link ForegroundServiceDelegationOptions#mClientPackageName} or the resolved
+ * package's UID is not same as {@link ForegroundServiceDelegationOptions#mClientUid}
+ */
+ boolean startForegroundServiceDelegateLocked(
+ @NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ Slog.v(TAG, "startForegroundServiceDelegateLocked " + options.getDescription());
+ final ComponentName cn = options.getComponentName();
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ ForegroundServiceDelegation delegation = mFgsDelegations.keyAt(i);
+ if (delegation.mOptions.isSameDelegate(options)) {
+ Slog.e(TAG, "startForegroundServiceDelegate " + options.getDescription()
+ + " already exists, multiple connections are not allowed");
+ return false;
+ }
+ }
+ final int callingPid = options.mClientPid;
+ final int callingUid = options.mClientUid;
+ final int userId = UserHandle.getUserId(callingUid);
+ final String callingPackage = options.mClientPackageName;
+
+ if (!canStartForegroundServiceLocked(callingPid, callingUid, callingPackage)) {
+ Slog.d(TAG, "startForegroundServiceDelegateLocked aborted,"
+ + " app is in the background");
+ return false;
+ }
+
+ IApplicationThread caller = options.mClientAppThread;
+ ProcessRecord callerApp;
+ if (caller != null) {
+ callerApp = mAm.getRecordForAppLOSP(caller);
+ } else {
+ synchronized (mAm.mPidsSelfLocked) {
+ callerApp = mAm.mPidsSelfLocked.get(callingPid);
+ caller = callerApp.getThread();
+ }
+ }
+ if (callerApp == null) {
+ throw new SecurityException(
+ "Unable to find app for caller " + caller
+ + " (pid=" + callingPid
+ + ") when startForegroundServiceDelegateLocked " + cn);
+ }
+
+ Intent intent = new Intent();
+ intent.setComponent(cn);
+ ServiceLookupResult res = retrieveServiceLocked(intent, null /*instanceName */,
+ false /* isSdkSandboxService */, INVALID_UID /* sdkSandboxClientAppUid */,
+ null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
+ callingPid, callingUid, userId, true /* createIfNeeded */,
+ false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
+ options);
+ if (res == null || res.record == null) {
+ Slog.d(TAG,
+ "startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
+ return false;
+ }
+
+ final ServiceRecord r = res.record;
+ r.setProcess(callerApp, caller, callingPid, null);
+ r.mIsFgsDelegate = true;
+ final ForegroundServiceDelegation delegation =
+ new ForegroundServiceDelegation(options, connection);
+ r.mFgsDelegation = delegation;
+ mFgsDelegations.put(delegation, r);
+ r.isForeground = true;
+ r.mFgsEnterTime = SystemClock.uptimeMillis();
+ r.foregroundServiceType = options.mForegroundServiceTypes;
+ setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
+ false, false);
+ final ProcessServiceRecord psr = callerApp.mServices;
+ final boolean newService = psr.startService(r);
+ // updateOomAdj.
+ updateServiceForegroundLocked(psr, /* oomAdj= */ true);
+
+ synchronized (mAm.mProcessStats.mLock) {
+ final ServiceState stracker = r.getTracker();
+ if (stracker != null) {
+ stracker.setForeground(true,
+ mAm.mProcessStats.getMemFactorLocked(),
+ SystemClock.uptimeMillis());
+ }
+ }
+
+ mAm.mBatteryStatsService.noteServiceStartRunning(callingUid, callingPackage,
+ cn.getClassName());
+ mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+ AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
+ true, false, null, false,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ registerAppOpCallbackLocked(r);
+ logFGSStateChangeLocked(r,
+ FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
+ // Notify the caller.
+ if (connection != null) {
+ mAm.mHandler.post(() -> {
+ connection.onServiceConnected(cn, delegation.mBinder);
+ });
+ }
+ signalForegroundServiceObserversLocked(r);
+ return true;
+ }
+
+ /**
+ * Stop the foreground service delegate. This removes the process out of foreground service
+ * process state.
+ *
+ * @param options an ForegroundServiceDelegationOptions object.
+ */
+ void stopForegroundServiceDelegateLocked(@NonNull ForegroundServiceDelegationOptions options) {
+ ServiceRecord r = null;
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ if (mFgsDelegations.keyAt(i).mOptions.isSameDelegate(options)) {
+ Slog.d(TAG, "stopForegroundServiceDelegateLocked " + options.getDescription());
+ r = mFgsDelegations.valueAt(i);
+ break;
+ }
+ }
+ if (r != null) {
+ bringDownServiceLocked(r, false);
+ } else {
+ Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist "
+ + options.getDescription());
+ }
+ }
+
+ /**
+ * Stop the foreground service delegate by its ServiceConnection.
+ * This removes the process out of foreground service process state.
+ *
+ * @param connection an ServiceConnection object.
+ */
+ void stopForegroundServiceDelegateLocked(@NonNull ServiceConnection connection) {
+ ServiceRecord r = null;
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ final ForegroundServiceDelegation d = mFgsDelegations.keyAt(i);
+ if (d.mConnection == connection) {
+ Slog.d(TAG, "stopForegroundServiceDelegateLocked "
+ + d.mOptions.getDescription());
+ r = mFgsDelegations.valueAt(i);
+ break;
+ }
+ }
+ if (r != null) {
+ bringDownServiceLocked(r, false);
+ } else {
+ Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 16fe121..8b5b795 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -249,6 +249,18 @@
private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes";
/**
+ * Enables proactive killing of cached apps
+ */
+ private static final String KEY_PROACTIVE_KILLS_ENABLED = "proactive_kills_enabled";
+
+ /**
+ * Trim LRU cached app when swap falls below this minimum percentage.
+ *
+ * Depends on KEY_PROACTIVE_KILLS_ENABLED
+ */
+ private static final String KEY_LOW_SWAP_THRESHOLD_PERCENT = "low_swap_threshold_percent";
+
+ /**
* Default value for mFlagBackgroundActivityStartsEnabled if not explicitly set in
* Settings.Global. This allows it to be set experimentally unless it has been
* enabled/disabled in developer options. Defaults to false.
@@ -306,6 +318,12 @@
"deferred_fgs_notification_interval";
/**
+ * Same as {@link #KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL} but for "short FGS".
+ */
+ private static final String KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT =
+ "deferred_fgs_notification_interval_for_short";
+
+ /**
* Time in milliseconds; once an FGS notification for a given uid has been
* deferred, no subsequent FGS notification from that uid will be deferred
* until this amount of time has passed. Default is two minutes
@@ -315,6 +333,12 @@
"deferred_fgs_notification_exclusion_time";
/**
+ * Same as {@link #KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME} but for "short FGS".
+ */
+ private static final String KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT =
+ "deferred_fgs_notification_exclusion_time_for_short";
+
+ /**
* Default value for mPushMessagingOverQuotaBehavior if not explicitly set in
* Settings.Global.
*/
@@ -571,11 +595,22 @@
// the foreground state.
volatile long mFgsNotificationDeferralInterval = 10_000;
+ /**
+ * Same as {@link #mFgsNotificationDeferralInterval} but used for "short FGS".
+ */
+ volatile long mFgsNotificationDeferralIntervalForShort = mFgsNotificationDeferralInterval;
+
// Rate limit: minimum time after an app's FGS notification is deferred
// before another FGS notification from that app can be deferred.
volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L;
/**
+ * Same as {@link #mFgsNotificationDeferralExclusionTime} but used for "short FGS".
+ */
+ volatile long mFgsNotificationDeferralExclusionTimeForShort =
+ mFgsNotificationDeferralExclusionTime;
+
+ /**
* When server pushing message is over the quote, select one of the temp allow list type as
* defined in {@link PowerExemptionManager.TempAllowListType}
*/
@@ -874,6 +909,10 @@
*/
private static final long DEFAULT_MIN_ASSOC_LOG_DURATION = 5 * 60 * 1000; // 5 mins
+ private static final boolean DEFAULT_PROACTIVE_KILLS_ENABLED = false;
+
+ private static final float DEFAULT_LOW_SWAP_THRESHOLD_PERCENT = 0.10f;
+
private static final String KEY_MIN_ASSOC_LOG_DURATION = "min_assoc_log_duration";
public static long MIN_ASSOC_LOG_DURATION = DEFAULT_MIN_ASSOC_LOG_DURATION;
@@ -904,6 +943,34 @@
public static boolean BINDER_HEAVY_HITTER_AUTO_SAMPLER_ENABLED;
public static int BINDER_HEAVY_HITTER_AUTO_SAMPLER_BATCHSIZE;
public static float BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD;
+ public static boolean PROACTIVE_KILLS_ENABLED = DEFAULT_PROACTIVE_KILLS_ENABLED;
+ public static float LOW_SWAP_THRESHOLD_PERCENT = DEFAULT_LOW_SWAP_THRESHOLD_PERCENT;
+
+ /** Timeout for a "short service" FGS, in milliseconds. */
+ private static final String KEY_SHORT_FGS_TIMEOUT_DURATION =
+ "short_fgs_timeout_duration";
+
+ /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
+ static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 60_000;
+
+ /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
+ public static volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
+
+ /**
+ * If a "short service" doesn't finish within this after the timeout (
+ * {@link #KEY_SHORT_FGS_TIMEOUT_DURATION}), then we'll declare an ANR.
+ * i.e. if the timeout is 60 seconds, and this ANR extra duration is 5 seconds, then
+ * the app will be ANR'ed in 65 seconds after a short service starts and it's not stopped.
+ */
+ private static final String KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION =
+ "short_fgs_anr_extra_wait_duration";
+
+ /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
+ static final long DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION = 5_000;
+
+ /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
+ public static volatile long mShortFgsAnrExtraWaitDuration =
+ DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@@ -944,6 +1011,12 @@
case KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME:
updateFgsNotificationDeferralExclusionTime();
break;
+ case KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT:
+ updateFgsNotificationDeferralIntervalForShort();
+ break;
+ case KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT:
+ updateFgsNotificationDeferralExclusionTimeForShort();
+ break;
case KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR:
updatePushMessagingOverQuotaBehavior();
break;
@@ -1040,6 +1113,18 @@
case KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS:
updateMaxServiceConnectionsPerProcess();
break;
+ case KEY_SHORT_FGS_TIMEOUT_DURATION:
+ updateShortFgsTimeoutDuration();
+ break;
+ case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
+ updateShortFgsAnrExtraWaitDuration();
+ break;
+ case KEY_PROACTIVE_KILLS_ENABLED:
+ updateProactiveKillsEnabled();
+ break;
+ case KEY_LOW_SWAP_THRESHOLD_PERCENT:
+ updateLowSwapThresholdPercent();
+ break;
default:
break;
}
@@ -1350,6 +1435,13 @@
/*default value*/ 10_000L);
}
+ private void updateFgsNotificationDeferralIntervalForShort() {
+ mFgsNotificationDeferralIntervalForShort = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT,
+ /*default value*/ 10_000L);
+ }
+
private void updateFgsNotificationDeferralExclusionTime() {
mFgsNotificationDeferralExclusionTime = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1357,6 +1449,13 @@
/*default value*/ 2 * 60 * 1000L);
}
+ private void updateFgsNotificationDeferralExclusionTimeForShort() {
+ mFgsNotificationDeferralExclusionTimeForShort = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT,
+ /*default value*/ 2 * 60 * 1000L);
+ }
+
private void updatePushMessagingOverQuotaBehavior() {
mPushMessagingOverQuotaBehavior = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1660,6 +1759,20 @@
CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
}
+ private void updateProactiveKillsEnabled() {
+ PROACTIVE_KILLS_ENABLED = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_PROACTIVE_KILLS_ENABLED,
+ DEFAULT_PROACTIVE_KILLS_ENABLED);
+ }
+
+ private void updateLowSwapThresholdPercent() {
+ LOW_SWAP_THRESHOLD_PERCENT = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_LOW_SWAP_THRESHOLD_PERCENT,
+ DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
+ }
+
private void updateMinAssocLogDuration() {
MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -1708,6 +1821,20 @@
DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS);
}
+ private void updateShortFgsTimeoutDuration() {
+ mShortFgsTimeoutDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_SHORT_FGS_TIMEOUT_DURATION,
+ DEFAULT_SHORT_FGS_TIMEOUT_DURATION);
+ }
+
+ private void updateShortFgsAnrExtraWaitDuration() {
+ mShortFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION,
+ DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
void dump(PrintWriter pw) {
pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -1860,6 +1987,30 @@
pw.print("="); pw.println(mNetworkAccessTimeoutMs);
pw.print(" "); pw.print(KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS);
pw.print("="); pw.println(mMaxServiceConnectionsPerProcess);
+ pw.print(" "); pw.print(KEY_PROACTIVE_KILLS_ENABLED);
+ pw.print("="); pw.println(PROACTIVE_KILLS_ENABLED);
+ pw.print(" "); pw.print(KEY_LOW_SWAP_THRESHOLD_PERCENT);
+ pw.print("="); pw.println(LOW_SWAP_THRESHOLD_PERCENT);
+
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATIONS_ENABLED);
+ pw.print("="); pw.println(mFlagFgsNotificationDeferralEnabled);
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATIONS_API_GATED);
+ pw.print("="); pw.println(mFlagFgsNotificationDeferralApiGated);
+
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL);
+ pw.print("="); pw.println(mFgsNotificationDeferralInterval);
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT);
+ pw.print("="); pw.println(mFgsNotificationDeferralIntervalForShort);
+
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME);
+ pw.print("="); pw.println(mFgsNotificationDeferralExclusionTime);
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT);
+ pw.print("="); pw.println(mFgsNotificationDeferralExclusionTimeForShort);
+
+ pw.print(" "); pw.print(KEY_SHORT_FGS_TIMEOUT_DURATION);
+ pw.print("="); pw.println(mShortFgsTimeoutDuration);
+ pw.print(" "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
+ pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 1d2c36b..9f2cc7f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
@@ -92,4 +93,28 @@
int clientAppUid, @NonNull String clientAppPackage, @NonNull String processName,
@Context.BindServiceFlags int flags)
throws RemoteException;
+
+ /**
+ * Start a foreground service delegate.
+ * @param options foreground service delegate options.
+ * @param connection a service connection served as callback to caller.
+ * @return true if delegate is started successfully, false otherwise.
+ * @hide
+ */
+ boolean startForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection);
+
+ /**
+ * Stop a foreground service delegate.
+ * @param options the foreground service delegate options.
+ * @hide
+ */
+ void stopForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options);
+
+ /**
+ * Stop a foreground service delegate by service connection.
+ * @param connection service connection used to start delegate previously.
+ * @hide
+ */
+ void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9718e67..6394d77 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -181,6 +181,7 @@
import android.app.ApplicationExitInfo;
import android.app.ApplicationThreadConstants;
import android.app.BroadcastOptions;
+import android.app.ComponentOptions;
import android.app.ContentProviderHolder;
import android.app.IActivityController;
import android.app.IActivityManager;
@@ -378,12 +379,10 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
@@ -8402,13 +8401,10 @@
// On Automotive / Headless System User Mode, at this point the system user has already been
// started and unlocked, and some of the tasks we do here have already been done. So skip
- // those in that case.
+ // those in that case. The duplicate system user start is guarded in SystemServiceManager.
// TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
- // start logic to UserManager-land
- final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
- if (bootingSystemUser) {
- mUserController.onSystemUserStarting();
- }
+ // start logic to UserManager-land.
+ mUserController.onSystemUserStarting();
synchronized (this) {
// Only start up encryption-aware persistent apps; once user is
@@ -8438,7 +8434,15 @@
t.traceEnd();
}
- if (bootingSystemUser) {
+ // Some systems - like automotive - will explicitly unlock system user then switch
+ // to a secondary user. Hence, we don't want to send duplicate broadcasts for
+ // the system user here.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
+ && !UserManager.isHeadlessSystemUserMode();
+
+ if (isBootingSystemUser) {
t.traceBegin("startHomeOnAllDisplays");
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
t.traceEnd();
@@ -8449,7 +8453,7 @@
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserStartBroadcast");
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -8490,7 +8494,7 @@
mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserSwitchBroadcasts");
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
t.traceEnd();
@@ -11173,9 +11177,9 @@
pw.printf("%s%s: %-60s (%s in swap)\n", prefix, stringifyKBSize(mi.pss),
mi.label, stringifyKBSize(mi.swapPss));
} else {
- pw.printf("%s%s: %s %s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
+ pw.printf("%s%s: %s%s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
mi.label,
- mi.userId != UserHandle.USER_SYSTEM ? "(user " + mi.userId + ")" : "");
+ mi.userId != UserHandle.USER_SYSTEM ? " (user " + mi.userId + ")" : "");
}
} else if (mi.isProc) {
pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
@@ -13506,9 +13510,19 @@
// Don't enforce the flag check if we're EITHER registering for only protected
// broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
// not be used generally, so we will be marking them as exported by default
- final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+ boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
&& mConstants.mEnforceReceiverExportedFlagRequirement;
+ // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
+ // updating their receivers to be exempt from this requirement until their receivers
+ // are flagged.
+ if (requireExplicitFlagForDynamicReceivers) {
+ if ("com.google.android.apps.messaging".equals(callerPackage)) {
+ // Note, a versionCode check for this package is not performed because it could
+ // cause breakage with a subsequent update outside the system image.
+ requireExplicitFlagForDynamicReceivers = false;
+ }
+ }
if (!onlyProtectedBroadcasts) {
if (receiver == null && !explicitExportStateDefined) {
// sticky broadcast, no flag specified (flag isn't required)
@@ -13901,10 +13915,10 @@
throw new SecurityException(
"Non-system callers may not flag broadcasts as alarm");
}
- if (options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
+ if (options.containsKey(ComponentOptions.KEY_INTERACTIVE)) {
enforceCallingPermission(
- android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
- "setInteractiveBroadcast");
+ android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE,
+ "setInteractive");
}
}
}
@@ -17431,6 +17445,21 @@
}
@Override
+ public int broadcastIntentWithCallback(Intent intent,
+ IIntentReceiver resultTo,
+ String[] requiredPermissions,
+ int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions) {
+ // Sending broadcasts with a finish callback without the need for the broadcasts
+ // delivery to be serialized is only supported by modern queue. So, when modern
+ // queue is disabled, we continue to send broadcasts in a serialized fashion.
+ final boolean serialized = !isModernQueueEnabled();
+ return broadcastIntent(intent, resultTo, requiredPermissions, serialized, userId,
+ appIdAllowList, filterExtrasForReceiver, bOptions);
+ }
+
+ @Override
public ComponentName startServiceInPackage(int uid, Intent service, String resolvedType,
boolean fgRequired, String callingPackage, @Nullable String callingFeatureId,
int userId, boolean allowBackgroundActivityStarts,
@@ -17773,14 +17802,6 @@
}
@Override
- public void stopForegroundServicesForChannel(String pkg, int userId,
- String channelId) {
- synchronized (ActivityManagerService.this) {
- mServices.stopForegroundServicesForChannelLocked(pkg, userId, channelId);
- }
- }
-
- @Override
public void registerProcessObserver(IProcessObserver processObserver) {
ActivityManagerService.this.registerProcessObserver(processObserver);
}
@@ -18097,6 +18118,30 @@
mUidObserverController.register(observer, which, cutpoint, callingPackage,
Binder.getCallingUid());
}
+
+ @Override
+ public boolean startForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ synchronized (ActivityManagerService.this) {
+ return mServices.startForegroundServiceDelegateLocked(options, connection);
+ }
+ }
+
+ @Override
+ public void stopForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options) {
+ synchronized (ActivityManagerService.this) {
+ mServices.stopForegroundServiceDelegateLocked(options);
+ }
+ }
+
+ @Override
+ public void stopForegroundServiceDelegate(@NonNull ServiceConnection connection) {
+ synchronized (ActivityManagerService.this) {
+ mServices.stopForegroundServiceDelegateLocked(connection);
+ }
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18286,6 +18331,59 @@
}
/**
+ * Start/stop foreground service delegate on a app's process.
+ * This interface is intended for the shell command to use.
+ */
+ void setForegroundServiceDelegate(String packageName, int uid, boolean isStart,
+ @ForegroundServiceDelegationOptions.DelegationService int delegateService,
+ String clientInstanceName) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != SYSTEM_UID && callingUid != ROOT_UID && callingUid != SHELL_UID) {
+ throw new SecurityException(
+ "No permission to start/stop foreground service delegate");
+ }
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ boolean foundPid = false;
+ synchronized (this) {
+ ArrayList<ForegroundServiceDelegationOptions> delegates = new ArrayList<>();
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+ final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+ final IApplicationThread thread = p.getThread();
+ if (p.uid == uid && thread != null) {
+ foundPid = true;
+ int pid = mPidsSelfLocked.keyAt(i);
+ ForegroundServiceDelegationOptions options =
+ new ForegroundServiceDelegationOptions(pid, uid, packageName,
+ null /* clientAppThread */,
+ false /* isSticky */,
+ clientInstanceName, 0 /* foregroundServiceType */,
+ delegateService);
+ delegates.add(options);
+ }
+ }
+ }
+ for (int i = delegates.size() - 1; i >= 0; i--) {
+ final ForegroundServiceDelegationOptions options = delegates.get(i);
+ if (isStart) {
+ ((ActivityManagerLocal) mInternal).startForegroundServiceDelegate(options,
+ null /* connection */);
+ } else {
+ ((ActivityManagerLocal) mInternal).stopForegroundServiceDelegate(options);
+ }
+ }
+ }
+ if (!foundPid) {
+ Slog.e(TAG, "setForegroundServiceDelegate can not find process for packageName:"
+ + packageName + " uid:" + uid);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
* Force the settings cache to be loaded
*/
void refreshSettingsCache() {
@@ -18889,19 +18987,20 @@
}
@Override
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId,
- @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
- Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+ @NonNull UndecFunction<IBinder, Integer, AttributionSource,
+ Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
+ SyncNotedAppOp> superImpl) {
if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(code, new AttributionSource(shellUid,
+ return superImpl.apply(clientId, code, new AttributionSource(shellUid,
"com.android.shell", attributionSource.getAttributionTag(),
attributionSource.getToken(), attributionSource.getNext()),
startIfModeDefault, shouldCollectAsyncNotedOp, message,
@@ -18911,21 +19010,22 @@
Binder.restoreCallingIdentity(identity);
}
}
- return superImpl.apply(code, attributionSource, startIfModeDefault,
+ return superImpl.apply(clientId, code, attributionSource, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
@Override
- public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
- Boolean, Void> superImpl) {
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+ @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+ Void> superImpl) {
if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- superImpl.apply(code, new AttributionSource(shellUid,
+ superImpl.apply(clientId, code, new AttributionSource(shellUid,
"com.android.shell", attributionSource.getAttributionTag(),
attributionSource.getToken(), attributionSource.getNext()),
skipProxyOperation);
@@ -18933,7 +19033,7 @@
Binder.restoreCallingIdentity(identity);
}
}
- superImpl.apply(code, attributionSource, skipProxyOperation);
+ superImpl.apply(clientId, code, attributionSource, skipProxyOperation);
}
private boolean isTargetOp(int code) {
@@ -19035,6 +19135,10 @@
return mOomAdjuster.mCachedAppOptimizer.useFreezer();
}
+ public boolean isAppFreezerExemptInstPkg() {
+ return mOomAdjuster.mCachedAppOptimizer.freezerExemptInstPkg();
+ }
+
/**
* Resets the state of the {@link com.android.server.am.AppErrors} instance.
* This is intended for testing within the CTS only and is protected by
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e4f947d..10f5a36 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -369,6 +369,8 @@
return runResetDropboxRateLimiter();
case "list-secondary-displays-for-starting-users":
return runListSecondaryDisplaysForStartingUsers(pw);
+ case "set-foreground-service-delegate":
+ return runSetForegroundServiceDelegate(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -3592,6 +3594,45 @@
return 0;
}
+ int runSetForegroundServiceDelegate(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ final String packageName = getNextArgRequired();
+ final String action = getNextArgRequired();
+ boolean isStart = true;
+ if ("start".equals(action)) {
+ isStart = true;
+ } else if ("stop".equals(action)) {
+ isStart = false;
+ } else {
+ pw.println("Error: action is either start or stop");
+ return -1;
+ }
+
+ int uid = INVALID_UID;
+ try {
+ final PackageManager pm = mInternal.mContext.getPackageManager();
+ uid = pm.getPackageUidAsUser(packageName,
+ PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Error: userId:" + userId + " package:" + packageName + " is not found");
+ return -1;
+ }
+ mInternal.setForegroundServiceDelegate(packageName, uid, isStart,
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SPECIAL_USE,
+ "FgsDelegate");
+ return 0;
+ }
+
int runResetDropboxRateLimiter() throws RemoteException {
mInternal.resetDropboxRateLimiter();
return 0;
@@ -3968,6 +4009,8 @@
pw.println(" list-secondary-displays-for-starting-users");
pw.println(" Lists the id of displays that can be used to start users on "
+ "background.");
+ pw.println(" set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
+ pw.println(" Start/stop an app's foreground service delegate.");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 50515cd..1f98aba 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -16,10 +16,10 @@
package com.android.server.am;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPES_MAX_INDEX;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
-import static android.content.pm.ServiceInfo.NUM_OF_FOREGROUND_SERVICE_TYPES;
import static android.content.pm.ServiceInfo.foregroundServiceTypeToLabel;
import static android.os.PowerExemptionManager.REASON_DENIED;
@@ -645,7 +645,7 @@
PackageDurations(int uid, String packageName,
MaxTrackingDurationConfig maxTrackingDurationConfig, AppFGSTracker tracker) {
- super(uid, packageName, NUM_OF_FOREGROUND_SERVICE_TYPES + 1, TAG,
+ super(uid, packageName, FOREGROUND_SERVICE_TYPES_MAX_INDEX + 1, TAG,
maxTrackingDurationConfig);
mEvents[DEFAULT_INDEX] = new LinkedList<>();
mTracker = tracker;
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index b7de57f8..45b11e1 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -506,10 +506,14 @@
}
if (profile != null) {
long startTime = SystemClock.currentThreadTimeMillis();
- // skip background PSS calculation of apps that are capturing
- // camera imagery
- final boolean usingCamera = mService.isCameraActiveForUid(profile.mApp.uid);
- long pss = usingCamera ? 0 : Debug.getPss(pid, tmp, null);
+ // skip background PSS calculation under the following situations:
+ // - app is capturing camera imagery
+ // - app is frozen and we have already collected PSS once.
+ final boolean skipPSSCollection =
+ (profile.mApp.mOptRecord != null
+ && profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen())
+ || mService.isCameraActiveForUid(profile.mApp.uid);
+ long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null);
long endTime = SystemClock.currentThreadTimeMillis();
synchronized (mProfilerLock) {
if (pss != 0 && profile.getThread() != null
@@ -524,7 +528,7 @@
if (DEBUG_PSS) {
Slog.d(TAG_PSS, "Skipped pss collection of " + pid
+ ": " + (profile.getThread() == null ? "NO_THREAD " : "")
- + (usingCamera ? "CAMERA " : "")
+ + (skipPSSCollection ? "SKIP_PSS_COLLECTION " : "")
+ (profile.getPid() != pid ? "PID_CHANGED " : "")
+ " initState=" + procState + " curState="
+ profile.getSetProcState() + " "
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index ba1c3b3..6abf6d8 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2784,6 +2784,37 @@
*/
@ReasonCode
int getBackgroundRestrictionExemptionReason(int uid) {
+ @ReasonCode int reason = getPotentialSystemExemptionReason(uid);
+ if (reason != REASON_DENIED) {
+ return reason;
+ }
+ final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+ if (packages != null) {
+ // Check each packages to see if any of them is in the "fixed" exemption cases.
+ for (String pkg : packages) {
+ reason = getPotentialSystemExemptionReason(uid, pkg);
+ if (reason != REASON_DENIED) {
+ return reason;
+ }
+ }
+ // Loop the packages again, and check the user-configurable exemptions.
+ for (String pkg : packages) {
+ reason = getPotentialUserAllowedExemptionReason(uid, pkg);
+ if (reason != REASON_DENIED) {
+ return reason;
+ }
+ }
+ }
+ return REASON_DENIED;
+ }
+
+ /**
+ * @param uid The uid to check.
+ * @return The potential exemption reason of the given uid. The caller must decide
+ * whether or not it should be exempted.
+ */
+ @ReasonCode
+ int getPotentialSystemExemptionReason(int uid) {
if (UserHandle.isCore(uid)) {
return REASON_SYSTEM_UID;
}
@@ -2811,37 +2842,51 @@
} else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
return REASON_PROC_STATE_PERSISTENT_UI;
}
- final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
- if (packages != null) {
- final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
- final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
- final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
- // Check each packages to see if any of them is in the "fixed" exemption cases.
- for (String pkg : packages) {
- if (isSystemModule(pkg)) {
- return REASON_SYSTEM_MODULE;
- } else if (isCarrierApp(pkg)) {
- return REASON_CARRIER_PRIVILEGED_APP;
- } else if (isExemptedFromSysConfig(pkg)) {
- return REASON_SYSTEM_ALLOW_LISTED;
- } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
- return REASON_SYSTEM_ALLOW_LISTED;
- } else if (pm.isPackageStateProtected(pkg, userId)) {
- return REASON_DPO_PROTECTED_APP;
- } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
- return REASON_ACTIVE_DEVICE_ADMIN;
- }
- }
- // Loop the packages again, and check the user-configurable exemptions.
- for (String pkg : packages) {
- if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_VPN;
- } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_PLATFORM_VPN;
- }
- }
+ return REASON_DENIED;
+ }
+
+ /**
+ * @param uid The uid to check.
+ * @param pkgName The package name to check.
+ * @return The potential system-fixed exemption reason of the given uid/package. The caller
+ * must decide whether or not it should be exempted.
+ */
+ @ReasonCode
+ int getPotentialSystemExemptionReason(int uid, String pkg) {
+ final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+ final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ final int userId = UserHandle.getUserId(uid);
+ if (isSystemModule(pkg)) {
+ return REASON_SYSTEM_MODULE;
+ } else if (isCarrierApp(pkg)) {
+ return REASON_CARRIER_PRIVILEGED_APP;
+ } else if (isExemptedFromSysConfig(pkg)) {
+ return REASON_SYSTEM_ALLOW_LISTED;
+ } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
+ return REASON_SYSTEM_ALLOW_LISTED;
+ } else if (pm.isPackageStateProtected(pkg, userId)) {
+ return REASON_DPO_PROTECTED_APP;
+ } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
+ return REASON_ACTIVE_DEVICE_ADMIN;
+ }
+ return REASON_DENIED;
+ }
+
+ /**
+ * @param uid The uid to check.
+ * @param pkgName The package name to check.
+ * @return The potential user-allowed exemption reason of the given uid/package. The caller
+ * must decide whether or not it should be exempted.
+ */
+ @ReasonCode
+ int getPotentialUserAllowedExemptionReason(int uid, String pkg) {
+ final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
+ if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_VPN;
+ } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_PLATFORM_VPN;
}
if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
return REASON_ROLE_DIALER;
@@ -2852,6 +2897,7 @@
if (isOnDeviceIdleAllowlist(uid)) {
return REASON_ALLOWLISTED_PACKAGE;
}
+ final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
return REASON_COMPANION_DEVICE_MANAGER;
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 9d96008..04ba757 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -136,12 +136,22 @@
private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true;
/**
- * For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
- * dispatch broadcasts to simultaneously.
+ * For {@link BroadcastQueueModernImpl}: Maximum dispatch parallelism
+ * that we'll tolerate for ordinary broadcast dispatch.
*/
public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES;
private static final String KEY_MAX_RUNNING_PROCESS_QUEUES = "bcast_max_running_process_queues";
- private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES = 4;
+ private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES =
+ ActivityManager.isLowRamDeviceStatic() ? 2 : 4;
+
+ /**
+ * For {@link BroadcastQueueModernImpl}: Additional running process queue parallelism beyond
+ * {@link #MAX_RUNNING_PROCESS_QUEUES} for dispatch of "urgent" broadcasts.
+ */
+ public int EXTRA_RUNNING_URGENT_PROCESS_QUEUES = DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES;
+ private static final String KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES =
+ "bcast_extra_running_urgent_process_queues";
+ private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
@@ -150,7 +160,8 @@
*/
public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts";
- private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
+ private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS =
+ ActivityManager.isLowRamDeviceStatic() ? 8 : 16;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of pending
@@ -159,7 +170,8 @@
*/
public int MAX_PENDING_BROADCASTS = DEFAULT_MAX_PENDING_BROADCASTS;
private static final String KEY_MAX_PENDING_BROADCASTS = "bcast_max_pending_broadcasts";
- private static final int DEFAULT_MAX_PENDING_BROADCASTS = 256;
+ private static final int DEFAULT_MAX_PENDING_BROADCASTS =
+ ActivityManager.isLowRamDeviceStatic() ? 128 : 256;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to normal
@@ -167,7 +179,7 @@
*/
public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
- private static final long DEFAULT_DELAY_NORMAL_MILLIS = 0;
+ private static final long DEFAULT_DELAY_NORMAL_MILLIS = +500;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
@@ -175,7 +187,7 @@
*/
public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
- private static final long DEFAULT_DELAY_CACHED_MILLIS = +30_000;
+ private static final long DEFAULT_DELAY_CACHED_MILLIS = +120_000;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to urgent
@@ -247,6 +259,10 @@
updateDeviceConfigConstants();
}
+ public int getMaxRunningQueues() {
+ return MAX_RUNNING_PROCESS_QUEUES + EXTRA_RUNNING_URGENT_PROCESS_QUEUES;
+ }
+
private void updateSettingsConstants() {
synchronized (this) {
try {
@@ -314,6 +330,9 @@
DEFAULT_MODERN_QUEUE_ENABLED);
MAX_RUNNING_PROCESS_QUEUES = getDeviceConfigInt(KEY_MAX_RUNNING_PROCESS_QUEUES,
DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
+ EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
+ KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES,
+ DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 47ca427..0f9c775 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -164,6 +164,7 @@
private boolean mProcessCached;
private boolean mProcessInstrumented;
+ private boolean mProcessPersistent;
private String mCachedToString;
private String mCachedToShortString;
@@ -323,8 +324,10 @@
this.app = app;
if (app != null) {
setProcessInstrumented(app.getActiveInstrumentation() != null);
+ setProcessPersistent(app.isPersistent());
} else {
setProcessInstrumented(false);
+ setProcessPersistent(false);
}
}
@@ -352,6 +355,17 @@
}
/**
+ * Update if this process is in the "persistent" state, which signals broadcast dispatch should
+ * bypass all pauses or delays to prevent the system from becoming out of sync with itself.
+ */
+ public void setProcessPersistent(boolean persistent) {
+ if (mProcessPersistent != persistent) {
+ mProcessPersistent = persistent;
+ invalidateRunnableAt();
+ }
+ }
+
+ /**
* Return if we know of an actively running "warm" process for this queue.
*/
public boolean isProcessWarm() {
@@ -570,6 +584,14 @@
}
/**
+ * Report whether this queue is currently handling an urgent broadcast.
+ */
+ public boolean isPendingUrgent() {
+ BroadcastRecord next = peekNextBroadcastRecord();
+ return (next != null) ? next.isUrgent() : false;
+ }
+
+ /**
* Quickly determine if this queue has broadcasts that are still waiting to
* be delivered at some point in the future.
*/
@@ -582,20 +604,21 @@
* barrier timestamp that are still waiting to be delivered.
*/
public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
- if (mActive != null) {
- return mActive.enqueueTime > barrierTime;
- }
final SomeArgs next = mPending.peekFirst();
final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
final SomeArgs nextOffload = mPendingOffload.peekFirst();
- // Empty queue is past any barrier
- final boolean nextLater = (next == null)
+
+ // Empty records are always past any barrier
+ final boolean activeBeyond = (mActive == null)
+ || mActive.enqueueTime > barrierTime;
+ final boolean nextBeyond = (next == null)
|| ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
- final boolean nextUrgentLater = (nextUrgent == null)
+ final boolean nextUrgentBeyond = (nextUrgent == null)
|| ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
- final boolean nextOffloadLater = (nextOffload == null)
+ final boolean nextOffloadBeyond = (nextOffload == null)
|| ((BroadcastRecord) nextOffload.arg1).enqueueTime > barrierTime;
- return nextLater && nextUrgentLater && nextOffloadLater;
+
+ return activeBeyond && nextBeyond && nextUrgentBeyond && nextOffloadBeyond;
}
public boolean isRunnable() {
@@ -636,6 +659,7 @@
static final int REASON_MAX_PENDING = 3;
static final int REASON_BLOCKED = 4;
static final int REASON_INSTRUMENTED = 5;
+ static final int REASON_PERSISTENT = 6;
static final int REASON_CONTAINS_FOREGROUND = 10;
static final int REASON_CONTAINS_ORDERED = 11;
static final int REASON_CONTAINS_ALARM = 12;
@@ -652,6 +676,7 @@
REASON_MAX_PENDING,
REASON_BLOCKED,
REASON_INSTRUMENTED,
+ REASON_PERSISTENT,
REASON_CONTAINS_FOREGROUND,
REASON_CONTAINS_ORDERED,
REASON_CONTAINS_ALARM,
@@ -672,6 +697,7 @@
case REASON_MAX_PENDING: return "MAX_PENDING";
case REASON_BLOCKED: return "BLOCKED";
case REASON_INSTRUMENTED: return "INSTRUMENTED";
+ case REASON_PERSISTENT: return "PERSISTENT";
case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
@@ -731,6 +757,9 @@
} else if (mCountManifest > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_MANIFEST;
+ } else if (mProcessPersistent) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_PERSISTENT;
} else if (mProcessCached) {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 454b284..8ece180 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -42,16 +42,15 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
@@ -729,19 +728,14 @@
thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
data, extras, ordered, sticky, sendingUser,
app.mState.getReportedProcState());
- // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
- // DeadObjectException when the process isn't actually dead.
- //} catch (DeadObjectException ex) {
- // Failed to call into the process. It's dying so just let it die and move on.
- // throw ex;
} catch (RemoteException ex) {
// Failed to call into the process. It's either dying or wedged. Kill it gently.
synchronized (mService) {
final String msg = "Failed to schedule " + intent + " to " + receiver
+ " via " + app + ": " + ex;
Slog.w(TAG, msg);
- app.scheduleCrashLocked(msg,
- CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
+ app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
+ true);
}
throw ex;
}
@@ -1394,8 +1388,7 @@
final String msg = "Failed to schedule " + r.intent + " to " + info
+ " via " + app + ": " + e;
Slog.w(TAG, msg);
- app.scheduleCrashLocked(msg,
- CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
+ app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true);
} catch (RuntimeException e) {
Slog.wtf(TAG, "Failed sending broadcast to "
+ r.curComponent + " with " + r.intent, e);
@@ -1639,9 +1632,8 @@
}
Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
logBroadcastReceiverDiscardLocked(r);
- String anrMessage =
- "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs + "ms";
- TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
+ TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
+ timeoutDurationMs);
if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
BroadcastFilter bf = (BroadcastFilter) curReceiver;
if (bf.receiverList.pid != 0
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c3839a9..ad5aa87 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -42,9 +42,9 @@
import android.annotation.UptimeMillisLong;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.UidObserver;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
@@ -138,7 +138,7 @@
// We configure runnable size only once at boot; it'd be too complex to
// try resizing dynamically at runtime
- mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
+ mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
}
/**
@@ -239,7 +239,7 @@
}
case MSG_DELIVERY_TIMEOUT_SOFT: {
synchronized (mService) {
- deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
+ deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
}
return true;
}
@@ -293,6 +293,19 @@
}
/**
+ * Return the number of active queues that are delivering "urgent" broadcasts
+ */
+ private int getRunningUrgentCount() {
+ int count = 0;
+ for (int i = 0; i < mRunning.length; i++) {
+ if (mRunning[i] != null && mRunning[i].getActive().isUrgent()) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
* Return the first index of the given value contained inside
* {@link #mRunning}, otherwise {@code -1}.
*/
@@ -356,7 +369,15 @@
*/
@GuardedBy("mService")
private void updateRunningListLocked() {
- int avail = mRunning.length - getRunningSize();
+ // Allocated size here implicitly includes the extra reservation for urgent
+ // dispatches beyond the MAX_RUNNING_QUEUES soft limit for normal
+ // parallelism. If we're already dispatching some urgent broadcasts,
+ // count that against the extra first - its role is to permit progress of
+ // urgent broadcast traffic when the normal reservation is fully occupied
+ // with less-urgent dispatches, not to generally expand parallelism.
+ final int usedExtra = Math.min(getRunningUrgentCount(),
+ mConstants.EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
+ int avail = mRunning.length - getRunningSize() - usedExtra;
if (avail == 0) return;
final int cookie = traceBegin("updateRunningList");
@@ -382,6 +403,15 @@
continue;
}
+ // If we've hit the soft limit for non-urgent dispatch parallelism,
+ // only consider delivering from queues whose ready broadcast is urgent
+ if (getRunningSize() >= mConstants.MAX_RUNNING_PROCESS_QUEUES) {
+ if (!queue.isPendingUrgent()) {
+ queue = nextQueue;
+ continue;
+ }
+ }
+
// If queues beyond this point aren't ready to run yet, schedule
// another pass when they'll be runnable
if (runnableAt > now && !waitingFor) {
@@ -599,6 +629,7 @@
// If nothing to dispatch, send any pending result immediately
if (r.receivers.isEmpty()) {
scheduleResultTo(r);
+ notifyFinishBroadcast(r);
}
traceEnd(cookie);
@@ -745,9 +776,10 @@
if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
- final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
- mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
+ final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT
+ : mBgConstants.TIMEOUT);
+ mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler,
+ MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
}
if (r.allowBackgroundActivityStarts) {
@@ -799,8 +831,7 @@
final String msg = "Failed to schedule " + r + " to " + receiver
+ " via " + app + ": " + e;
logw(msg);
- app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
- app.setKilled(true);
+ app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true);
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
}
} else {
@@ -827,22 +858,24 @@
} catch (RemoteException e) {
final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
logw(msg);
- app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
+ app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true);
}
}
// Clear so both local and remote references can be GC'ed
r.resultTo = null;
}
- private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
+ private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
+ int softTimeoutMillis) {
if (queue.app != null) {
// Instead of immediately triggering an ANR, extend the timeout by
// the amount of time the process was runnable-but-waiting; we're
// only willing to do this once before triggering an hard ANR
final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
- final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
+ final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
+ Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue),
+ hardTimeoutMillis);
} else {
deliveryTimeoutHardLocked(queue);
}
@@ -903,8 +936,7 @@
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
if (app != null && !app.isDebugging()) {
- mService.appNotResponding(queue.app, TimeoutRecord
- .forBroadcastReceiver("Broadcast of " + r.toShortString()));
+ mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
}
} else {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
@@ -1402,30 +1434,34 @@
final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
- mService.notifyBroadcastFinishedLocked(r);
- mHistory.addBroadcastToHistoryLocked(r);
+ notifyFinishBroadcast(r);
+ }
+ }
- r.finishTime = SystemClock.uptimeMillis();
- r.nextReceiver = r.receivers.size();
- BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
+ mService.notifyBroadcastFinishedLocked(r);
+ mHistory.addBroadcastToHistoryLocked(r);
- if (r.intent.getComponent() == null && r.intent.getPackage() == null
- && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- int manifestCount = 0;
- int manifestSkipCount = 0;
- for (int i = 0; i < r.receivers.size(); i++) {
- if (r.receivers.get(i) instanceof ResolveInfo) {
- manifestCount++;
- if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
- manifestSkipCount++;
- }
+ r.finishTime = SystemClock.uptimeMillis();
+ r.nextReceiver = r.receivers.size();
+ BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+
+ if (r.intent.getComponent() == null && r.intent.getPackage() == null
+ && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ int manifestCount = 0;
+ int manifestSkipCount = 0;
+ for (int i = 0; i < r.receivers.size(); i++) {
+ if (r.receivers.get(i) instanceof ResolveInfo) {
+ manifestCount++;
+ if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
+ manifestSkipCount++;
}
}
-
- final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
- mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
- manifestCount, manifestSkipCount, dispatchTime);
}
+
+ final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
+ mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
+ manifestCount, manifestSkipCount, dispatchTime);
}
}
@@ -1448,7 +1484,7 @@
}
BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid);
- created.app = mService.getProcessRecordLocked(processName, uid);
+ created.setProcess(mService.getProcessRecordLocked(processName, uid));
if (leaf == null) {
mProcessQueues.put(uid, created);
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 6ea2dee..84d7442 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -397,7 +397,7 @@
alarm = options != null && options.isAlarmBroadcast();
pushMessage = options != null && options.isPushMessagingBroadcast();
pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
- interactive = options != null && options.isInteractiveBroadcast();
+ interactive = options != null && options.isInteractive();
this.filterExtrasForReceiver = filterExtrasForReceiver;
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index cbf0aae..4c10d58b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -89,6 +90,8 @@
"compact_proc_state_throttle";
@VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
"freeze_debounce_timeout";
+ @VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG =
+ "freeze_exempt_inst_pkg";
// RSS Indices
private static final int RSS_TOTAL_INDEX = 0;
@@ -137,6 +140,7 @@
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
@VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L;
+ @VisibleForTesting static final Boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
@VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@@ -277,6 +281,8 @@
for (String name : properties.getKeyset()) {
if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) {
updateFreezerDebounceTimeout();
+ } else if (KEY_FREEZER_EXEMPT_INST_PKG.equals(name)) {
+ updateFreezerExemptInstPkg();
}
}
}
@@ -357,6 +363,7 @@
private boolean mFreezerOverride = false;
@VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
+ @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG;
// Maps process ID to last compaction statistics for processes that we've fully compacted. Used
// when evaluating throttles that we only consider for "full" compaction, so we don't store
@@ -566,6 +573,15 @@
}
}
+ /**
+ * Returns whether freezer exempts INSTALL_PACKAGES.
+ */
+ public boolean freezerExemptInstPkg() {
+ synchronized (mPhenotypeFlagLock) {
+ return mUseFreezer && mFreezerExemptInstPkg;
+ }
+ }
+
@GuardedBy("mProcLock")
void dump(PrintWriter pw) {
pw.println("CachedAppOptimizer settings");
@@ -647,6 +663,7 @@
pw.println(" " + KEY_USE_FREEZER + "=" + mUseFreezer);
pw.println(" " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
pw.println(" " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
+ pw.println(" " + KEY_FREEZER_EXEMPT_INST_PKG + "=" + mFreezerExemptInstPkg);
synchronized (mProcLock) {
int size = mFrozenProcesses.size();
pw.println(" Apps frozen: " + size);
@@ -829,7 +846,7 @@
/**
* Retrieves the free swap percentage.
*/
- static private native double getFreeSwapPercent();
+ static native double getFreeSwapPercent();
/**
* Retrieves the total used physical ZRAM
@@ -1007,6 +1024,7 @@
KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
mUseFreezer = isFreezerSupported();
updateFreezerDebounceTimeout();
+ updateFreezerExemptInstPkg();
} else {
mUseFreezer = false;
}
@@ -1194,6 +1212,15 @@
if (mFreezerDebounceTimeout < 0) {
mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
}
+ Slog.d(TAG_AM, "Freezer timeout set to " + mFreezerDebounceTimeout);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateFreezerExemptInstPkg() {
+ mFreezerExemptInstPkg = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ KEY_FREEZER_EXEMPT_INST_PKG, DEFAULT_FREEZER_EXEMPT_INST_PKG);
+ Slog.d(TAG_AM, "Freezer exemption set to " + mFreezerExemptInstPkg);
}
private boolean parseProcStateThrottle(String procStateThrottleString) {
@@ -1977,6 +2004,7 @@
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(true);
+ opt.setHasCollectedFrozenPSS(false);
mFrozenProcesses.put(pid, proc);
} catch (Exception e) {
Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
@@ -2084,15 +2112,28 @@
@GuardedBy({"mAm"})
@Override
- public void onBlockingFileLock(int pid) {
+ public void onBlockingFileLock(IntArray pids) {
if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
+ Slog.d(TAG_AM, "Blocking file lock found: " + pids);
}
synchronized (mProcLock) {
+ int pid = pids.get(0);
ProcessRecord app = mFrozenProcesses.get(pid);
+ ProcessRecord pr;
if (app != null) {
- Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
- unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ for (int i = 1; i < pids.size(); i++) {
+ int blocked = pids.get(i);
+ synchronized (mAm.mPidsSelfLocked) {
+ pr = mAm.mPidsSelfLocked.get(blocked);
+ }
+ if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+ Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ + pr.processName + " (" + blocked + ")");
+ // Found at least one blocked non-cached process
+ unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ break;
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a97173d..16055b9 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -81,6 +81,7 @@
import com.android.server.LocalServices;
import com.android.server.RescueParty;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerService;
import com.android.server.pm.pkg.AndroidPackage;
import java.io.FileDescriptor;
@@ -163,7 +164,7 @@
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, int callingUid, String callingPackage, String callingTag,
boolean stable, int userId) {
- ContentProviderRecord cpr;
+ ContentProviderRecord cpr = null;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
boolean providerRunning = false;
@@ -185,8 +186,21 @@
checkTime(startTime, "getContentProviderImpl: getProviderByName");
- // First check if this content provider has been published...
- cpr = mProviderMap.getProviderByName(name, userId);
+ UserManagerService userManagerService = UserManagerService.getInstance();
+
+ /*
+ For clone user profile and allowed authority, skipping finding provider and redirecting
+ it to owner profile. Ideally clone profile should not have MediaProvider instance
+ installed and mProviderMap would not have entry for clone user. This is just fallback
+ check to ensure even if MediaProvider is installed in Clone Profile, it should not be
+ used and redirect to owner user's MediaProvider.
+ */
+ //todo(b/236121588) MediaProvider should not be installed in clone profile.
+ if (!isAuthorityRedirectedForCloneProfile(name)
+ || !userManagerService.isMediaSharedWithParent(userId)) {
+ // First check if this content provider has been published...
+ cpr = mProviderMap.getProviderByName(name, userId);
+ }
// If that didn't work, check if it exists for user 0 and then
// verify that it's a singleton provider before using it.
if (cpr == null && userId != UserHandle.USER_SYSTEM) {
@@ -201,11 +215,9 @@
userId = UserHandle.USER_SYSTEM;
checkCrossUser = false;
} else if (isAuthorityRedirectedForCloneProfile(name)) {
- UserManagerInternal umInternal = LocalServices.getService(
- UserManagerInternal.class);
- UserInfo userInfo = umInternal.getUserInfo(userId);
-
- if (userInfo != null && userInfo.isCloneProfile()) {
+ if (userManagerService.isMediaSharedWithParent(userId)) {
+ UserManagerInternal umInternal = LocalServices.getService(
+ UserManagerInternal.class);
userId = umInternal.getProfileParentId(userId);
checkCrossUser = false;
}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 60e6754..ea3c8dc 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -107,21 +107,21 @@
30079 uc_dispatch_user_switch (oldUserId|1|5),(newUserId|1|5)
30080 uc_continue_user_switch (oldUserId|1|5),(newUserId|1|5)
30081 uc_send_user_broadcast (userId|1|5),(IntentAction|3)
+
# Tags below are used by SystemServiceManager - although it's technically part of am, these are
# also user switch events and useful to be analyzed together with events above.
-30082 ssm_user_starting (userId|1|5),(visible|1)
+30082 ssm_user_starting (userId|1|5)
30083 ssm_user_switching (oldUserId|1|5),(newUserId|1|5)
30084 ssm_user_unlocking (userId|1|5)
30085 ssm_user_unlocked (userId|1|5)
-30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
+30086 ssm_user_stopping (userId|1|5)
30087 ssm_user_stopped (userId|1|5)
30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
-30089 ssm_user_visibility_changed (userId|1|5),(visible|1)
+
+# Similarly, tags below are used by UserManagerService
+30091 um_user_visibility_changed (userId|1|5),(visible|1)
# Foreground service start/stop events.
30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
-
-
-
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegation.java b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
new file mode 100644
index 0000000..a051d17
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
@@ -0,0 +1,40 @@
+/*
+ * 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.server.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * A foreground service delegate which has client options and connection callback.
+ */
+public class ForegroundServiceDelegation {
+ public final IBinder mBinder = new Binder();
+ @NonNull
+ public final ForegroundServiceDelegationOptions mOptions;
+ @Nullable
+ public final ServiceConnection mConnection;
+
+ public ForegroundServiceDelegation(@NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ mOptions = options;
+ mConnection = connection;
+ }
+}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
new file mode 100644
index 0000000..5eb5a55
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
@@ -0,0 +1,262 @@
+/*
+ * 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.server.am;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.IApplicationThread;
+import android.content.ComponentName;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A service module such as MediaSessionService, VOIP, Camera, Microphone, Location can ask
+ * ActivityManagerService to start a foreground service delegate on behalf of the actual app,
+ * by which the client app's process state can be promoted to FOREGROUND_SERVICE process state which
+ * is higher than the app's actual process state if the app is in the background. This can help to
+ * keep the app in the memory and extra run-time.
+ * The app does not need to define an actual service component nor add it into manifest file.
+ */
+public class ForegroundServiceDelegationOptions {
+
+ public static final int DELEGATION_SERVICE_DEFAULT = 0;
+ public static final int DELEGATION_SERVICE_DATA_SYNC = 1;
+ public static final int DELEGATION_SERVICE_MEDIA_PLAYBACK = 2;
+ public static final int DELEGATION_SERVICE_PHONE_CALL = 3;
+ public static final int DELEGATION_SERVICE_LOCATION = 4;
+ public static final int DELEGATION_SERVICE_CONNECTED_DEVICE = 5;
+ public static final int DELEGATION_SERVICE_MEDIA_PROJECTION = 6;
+ public static final int DELEGATION_SERVICE_CAMERA = 7;
+ public static final int DELEGATION_SERVICE_MICROPHONE = 8;
+ public static final int DELEGATION_SERVICE_HEALTH = 9;
+ public static final int DELEGATION_SERVICE_REMOTE_MESSAGING = 10;
+ public static final int DELEGATION_SERVICE_SYSTEM_EXEMPTED = 11;
+ public static final int DELEGATION_SERVICE_SPECIAL_USE = 12;
+
+ @IntDef(flag = false, prefix = { "DELEGATION_SERVICE_" }, value = {
+ DELEGATION_SERVICE_DEFAULT,
+ DELEGATION_SERVICE_DATA_SYNC,
+ DELEGATION_SERVICE_MEDIA_PLAYBACK,
+ DELEGATION_SERVICE_PHONE_CALL,
+ DELEGATION_SERVICE_LOCATION,
+ DELEGATION_SERVICE_CONNECTED_DEVICE,
+ DELEGATION_SERVICE_MEDIA_PROJECTION,
+ DELEGATION_SERVICE_CAMERA,
+ DELEGATION_SERVICE_MICROPHONE,
+ DELEGATION_SERVICE_HEALTH,
+ DELEGATION_SERVICE_REMOTE_MESSAGING,
+ DELEGATION_SERVICE_SYSTEM_EXEMPTED,
+ DELEGATION_SERVICE_SPECIAL_USE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DelegationService {}
+
+ // The actual app's PID
+ public final int mClientPid;
+ // The actual app's UID
+ public final int mClientUid;
+ // The actual app's package name
+ @NonNull
+ public final String mClientPackageName;
+ // The actual app's app thread
+ @Nullable
+ public final IApplicationThread mClientAppThread;
+ public final boolean mSticky; // Is it a sticky service
+
+ // The delegation service's instance name which is to identify the delegate.
+ @NonNull
+ public String mClientInstanceName;
+ // The foreground service types it consists of.
+ public final int mForegroundServiceTypes;
+ /**
+ * The service's name such as MediaSessionService, VOIP, Camera, Microphone, Location. This is
+ * the internal module's name which actually starts the FGS delegate on behalf of the client
+ * app.
+ */
+ public final @DelegationService int mDelegationService;
+
+ public ForegroundServiceDelegationOptions(int clientPid,
+ int clientUid,
+ @NonNull String clientPackageName,
+ @NonNull IApplicationThread clientAppThread,
+ boolean isSticky,
+ @NonNull String clientInstanceName,
+ int foregroundServiceTypes,
+ @DelegationService int delegationService) {
+ mClientPid = clientPid;
+ mClientUid = clientUid;
+ mClientPackageName = clientPackageName;
+ mClientAppThread = clientAppThread;
+ mSticky = isSticky;
+ mClientInstanceName = clientInstanceName;
+ mForegroundServiceTypes = foregroundServiceTypes;
+ mDelegationService = delegationService;
+ }
+
+ /**
+ * A service delegates a foreground service state to a clientUID using a instanceName.
+ * This delegation is uniquely identified by
+ * mDelegationService/mClientUid/mClientPid/mClientInstanceName
+ */
+ public boolean isSameDelegate(ForegroundServiceDelegationOptions that) {
+ return this.mDelegationService == that.mDelegationService
+ && this.mClientUid == that.mClientUid
+ && this.mClientPid == that.mClientPid
+ && this.mClientInstanceName.equals(that.mClientInstanceName);
+ }
+
+ /**
+ * Construct a component name for this delegate.
+ */
+ public ComponentName getComponentName() {
+ return new ComponentName(mClientPackageName, serviceCodeToString(mDelegationService)
+ + ":" + mClientInstanceName);
+ }
+
+ /**
+ * Get string description of this delegate options.
+ */
+ public String getDescription() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ForegroundServiceDelegate{")
+ .append("package:")
+ .append(mClientPackageName)
+ .append(",")
+ .append("service:")
+ .append(serviceCodeToString(mDelegationService))
+ .append(",")
+ .append("uid:")
+ .append(mClientUid)
+ .append(",")
+ .append("pid:")
+ .append(mClientPid)
+ .append(",")
+ .append("instance:")
+ .append(mClientInstanceName)
+ .append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Map the integer service code to string name.
+ * @param serviceCode
+ * @return
+ */
+ public static String serviceCodeToString(@DelegationService int serviceCode) {
+ switch (serviceCode) {
+ case DELEGATION_SERVICE_DEFAULT:
+ return "DEFAULT";
+ case DELEGATION_SERVICE_DATA_SYNC:
+ return "DATA_SYNC";
+ case DELEGATION_SERVICE_MEDIA_PLAYBACK:
+ return "MEDIA_PLAYBACK";
+ case DELEGATION_SERVICE_PHONE_CALL:
+ return "PHONE_CALL";
+ case DELEGATION_SERVICE_LOCATION:
+ return "LOCATION";
+ case DELEGATION_SERVICE_CONNECTED_DEVICE:
+ return "CONNECTED_DEVICE";
+ case DELEGATION_SERVICE_MEDIA_PROJECTION:
+ return "MEDIA_PROJECTION";
+ case DELEGATION_SERVICE_CAMERA:
+ return "CAMERA";
+ case DELEGATION_SERVICE_MICROPHONE:
+ return "MICROPHONE";
+ case DELEGATION_SERVICE_HEALTH:
+ return "HEALTH";
+ case DELEGATION_SERVICE_REMOTE_MESSAGING:
+ return "REMOTE_MESSAGING";
+ case DELEGATION_SERVICE_SYSTEM_EXEMPTED:
+ return "SYSTEM_EXEMPTED";
+ case DELEGATION_SERVICE_SPECIAL_USE:
+ return "SPECIAL_USE";
+ default:
+ return "(unknown:" + serviceCode + ")";
+ }
+ }
+
+ public static class Builder {
+ int mClientPid; // The actual app PID
+ int mClientUid; // The actual app UID
+ String mClientPackageName; // The actual app's package name
+ int mClientNotificationId; // The actual app's notification
+ IApplicationThread mClientAppThread; // The actual app's app thread
+ boolean mSticky; // Is it a sticky service
+ String mClientInstanceName; // The delegation service instance name
+ int mForegroundServiceTypes; // The foreground service types it consists of
+ @DelegationService int mDelegationService; // The internal service's name, i.e. VOIP
+
+ public Builder setClientPid(int clientPid) {
+ mClientPid = clientPid;
+ return this;
+ }
+
+ public Builder setClientUid(int clientUid) {
+ mClientUid = clientUid;
+ return this;
+ }
+
+ public Builder setClientPackageName(@NonNull String clientPackageName) {
+ mClientPackageName = clientPackageName;
+ return this;
+ }
+
+ public Builder setClientNotificationId(int clientNotificationId) {
+ mClientNotificationId = clientNotificationId;
+ return this;
+ }
+
+ public Builder setClientAppThread(@NonNull IApplicationThread clientAppThread) {
+ mClientAppThread = clientAppThread;
+ return this;
+ }
+
+ public Builder setClientInstanceName(@NonNull String clientInstanceName) {
+ mClientInstanceName = clientInstanceName;
+ return this;
+ }
+
+ public Builder setSticky(boolean isSticky) {
+ mSticky = isSticky;
+ return this;
+ }
+
+ public Builder setForegroundServiceTypes(int foregroundServiceTypes) {
+ mForegroundServiceTypes = foregroundServiceTypes;
+ return this;
+ }
+
+ public Builder setDelegationService(@DelegationService int delegationService) {
+ mDelegationService = delegationService;
+ return this;
+ }
+
+ public ForegroundServiceDelegationOptions build() {
+ return new ForegroundServiceDelegationOptions(mClientPid,
+ mClientUid,
+ mClientPackageName,
+ mClientAppThread,
+ mSticky,
+ mClientInstanceName,
+ mForegroundServiceTypes,
+ mDelegationService
+ );
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 68e5a5d..eb2b7d4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1118,6 +1118,12 @@
private long mNextNoKillDebugMessageTime;
+ private double mLastFreeSwapPercent = 1.00;
+
+ private static double getFreeSwapPercent() {
+ return CachedAppOptimizer.getFreeSwapPercent();
+ }
+
@GuardedBy({"mService", "mProcLock"})
private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
@@ -1142,6 +1148,11 @@
int numEmpty = 0;
int numTrimming = 0;
+ boolean proactiveKillsEnabled = mConstants.PROACTIVE_KILLS_ENABLED;
+ double lowSwapThresholdPercent = mConstants.LOW_SWAP_THRESHOLD_PERCENT;
+ double freeSwapPercent = proactiveKillsEnabled ? getFreeSwapPercent() : 1.00;
+ ProcessRecord lruCachedApp = null;
+
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
@@ -1179,6 +1190,8 @@
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
true);
+ } else if (proactiveKillsEnabled) {
+ lruCachedApp = app;
}
break;
case PROCESS_STATE_CACHED_EMPTY:
@@ -1198,6 +1211,8 @@
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
true);
+ } else if (proactiveKillsEnabled) {
+ lruCachedApp = app;
}
}
break;
@@ -1229,6 +1244,20 @@
}
}
+ if (proactiveKillsEnabled // Proactive kills enabled?
+ && doKillExcessiveProcesses // Should kill excessive processes?
+ && freeSwapPercent < lowSwapThresholdPercent // Swap below threshold?
+ && lruCachedApp != null // If no cached app, let LMKD decide
+ // If swap is non-decreasing, give reclaim a chance to catch up
+ && freeSwapPercent < mLastFreeSwapPercent) {
+ lruCachedApp.killLocked("swap low and too many cached",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
+ true);
+ }
+
+ mLastFreeSwapPercent = freeSwapPercent;
+
return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 3f5f3bc..fed0b11 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -396,11 +396,16 @@
resolvedType = key.requestResolvedType;
}
- // Apply any launch flags from the ActivityOptions. This is to ensure that the caller
- // can specify a consistent launch mode even if the PendingIntent is immutable
+ // Apply any launch flags from the ActivityOptions. This is used only by SystemUI
+ // to ensure that we can launch the pending intent with a consistent launch mode even
+ // if the provided PendingIntent is immutable (ie. to force an activity to launch into
+ // a new task, or to launch multiple instances if supported by the app)
final ActivityOptions opts = ActivityOptions.fromBundle(options);
if (opts != null) {
- finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
+ // TODO(b/254490217): Move this check into SafeActivityOptions
+ if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) {
+ finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
+ }
}
// Extract options before clearing calling identity
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index fb41a39..24cc533 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -73,6 +73,12 @@
private boolean mFrozen;
/**
+ * Set to false after the process has been frozen.
+ * Set to true after we have collected PSS for the frozen process.
+ */
+ private boolean mHasCollectedFrozenPSS;
+
+ /**
* An override on the freeze state is in progress.
*/
@GuardedBy("mProcLock")
@@ -188,6 +194,25 @@
mFrozen = frozen;
}
+ boolean skipPSSCollectionBecauseFrozen() {
+ boolean collected = mHasCollectedFrozenPSS;
+
+ // This check is racy but it isn't critical to PSS collection that we have the most up to
+ // date idea of whether a task is frozen.
+ if (!mFrozen) {
+ // not frozen == always ask to collect PSS
+ return false;
+ }
+
+ // We don't want to count PSS for a frozen process more than once.
+ mHasCollectedFrozenPSS = true;
+ return collected;
+ }
+
+ void setHasCollectedFrozenPSS(boolean collected) {
+ mHasCollectedFrozenPSS = collected;
+ }
+
@GuardedBy("mProcLock")
boolean hasFreezerOverride() {
return mFreezerOverride;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 42bfc4c..ecea96e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1693,7 +1693,8 @@
app.info.packageName);
externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid,
app.info.packageName);
- if (pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+ if (mService.isAppFreezerExemptInstPkg()
+ && pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
app.info.packageName, userId)
== PackageManager.PERMISSION_GRANTED) {
Slog.i(TAG, app.info.packageName + " is exempt from freezer");
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index d2ef479..2ad2077 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -30,6 +30,7 @@
import android.app.ActivityManager;
import android.content.ComponentName;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.Slog;
import android.util.TimeUtils;
@@ -43,6 +44,9 @@
* The state info of the process, including proc state, oom adj score, et al.
*/
final class ProcessStateRecord {
+ // Enable this to trace all OomAdjuster state transitions
+ private static final boolean TRACE_OOM_ADJ = false;
+
private final ProcessRecord mApp;
private final ActivityManagerService mService;
private final ActivityManagerGlobalLock mProcLock;
@@ -916,6 +920,12 @@
@GuardedBy("mService")
void setAdjType(String adjType) {
+ if (TRACE_OOM_ADJ) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "oom:" + mApp.processName + "/u" + mApp.uid, adjType, 0);
+ }
mAdjType = adjType;
}
@@ -1153,6 +1163,10 @@
@GuardedBy({"mService", "mProcLock"})
void onCleanupApplicationRecordLSP() {
+ if (TRACE_OOM_ADJ) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+ }
setHasForegroundActivities(false);
mHasShownUi = false;
mForcingToImportant = null;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4b82ad8..c27ed7a 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -206,6 +206,10 @@
// Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set.
long mLastSetFgsRestrictionTime;
+ // This is a service record of a FGS delegate (not a service record of a real service)
+ boolean mIsFgsDelegate;
+ @Nullable ForegroundServiceDelegation mFgsDelegation;
+
String stringName; // caching of toString
private int lastStartId; // identifier of most recent start request.
@@ -233,6 +237,7 @@
final boolean taskRemoved;
final int id;
final int callingId;
+ final String mCallingProcessName;
final Intent intent;
final NeededUriGrants neededGrants;
long deliveredTime;
@@ -242,14 +247,16 @@
String stringName; // caching of toString
- StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent,
- NeededUriGrants _neededGrants, int _callingId) {
+ StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id,
+ Intent _intent, NeededUriGrants _neededGrants, int _callingId,
+ String callingProcessName) {
sr = _sr;
taskRemoved = _taskRemoved;
id = _id;
intent = _intent;
neededGrants = _neededGrants;
callingId = _callingId;
+ mCallingProcessName = callingProcessName;
}
UriPermissionOwner getUriPermissionsLocked() {
@@ -502,6 +509,9 @@
pw.print(" foregroundId="); pw.print(foregroundId);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
}
+ if (mIsFgsDelegate) {
+ pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
+ }
pw.print(prefix); pw.print("createTime=");
TimeUtils.formatDuration(createRealTime, nowReal, pw);
pw.print(" startingBgTimeout=");
@@ -634,7 +644,9 @@
serviceInfo.applicationInfo.uid,
serviceInfo.applicationInfo.longVersionCode,
serviceInfo.processName, serviceInfo.name);
- tracker.applyNewOwner(this);
+ if (tracker != null) {
+ tracker.applyNewOwner(this);
+ }
}
return tracker;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index af55980..4d86140 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -134,6 +134,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -176,9 +177,7 @@
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
- static final int USER_VISIBLE_MSG = 150;
-
- private static final int NO_ARG2 = 0;
+ static final int USER_VISIBILITY_CHANGED_MSG = 150;
// Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
// the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -200,6 +199,14 @@
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
/**
+ * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
+ * called after dismissing the keyguard.
+ * Otherwise, we should move on to unfreeze the screen {@link #unfreezeScreen}
+ * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
+ */
+ private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
+
+ /**
* Time after last scheduleOnUserCompletedEvent() call at which USER_COMPLETED_EVENT_MSG will be
* scheduled (although it may fire sooner instead).
* When it fires, {@link #reportOnUserCompletedEvent} will be processed.
@@ -430,10 +437,13 @@
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
+ // TODO(b/244333150) remove this array and let UserVisibilityMediator call the listeners
+ // directly, as that class should be responsible for all user visibility logic (for example,
+ // when the foreground user is switched out, its profiles also become invisible)
/**
* List of visible users (as defined by {@link UserManager#isUserVisible()}).
*
- * <p>It's only used to call {@link SystemServiceManager} when the visibility is changed upon
+ * <p>It's only used to call {@link UserManagerInternal} when the visibility is changed upon
* the user starting or stopping.
*
* <p>Note: only the key is used, not the value.
@@ -1080,17 +1090,14 @@
// TODO(b/239982558): for now we're just updating the user's visibility, but most likely
// we'll need to remove this call and handle that as part of the user state workflow
// instead.
- userManagerInternal.unassignUserFromDisplay(userId);
+ userManagerInternal.unassignUserFromDisplayOnStop(userId);
final boolean visibilityChanged;
boolean visibleBefore;
synchronized (mLock) {
visibleBefore = mVisibleUsers.get(userId);
if (visibleBefore) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "Removing %d from mVisibleUsers", userId);
- }
- mVisibleUsers.delete(userId);
+ deleteVisibleUserLocked(userId);
visibilityChanged = true;
} else {
visibilityChanged = false;
@@ -1139,6 +1146,20 @@
}
}
+ private void addVisibleUserLocked(@UserIdInt int userId) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "adding %d to mVisibleUsers", userId);
+ }
+ mVisibleUsers.put(userId, true);
+ }
+
+ private void deleteVisibleUserLocked(@UserIdInt int userId) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "deleting %d from mVisibleUsers", userId);
+ }
+ mVisibleUsers.delete(userId);
+ }
+
private void finishUserStopping(final int userId, final UserState uss,
final boolean allowDelayedLocking, final boolean visibilityChanged) {
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
@@ -1157,7 +1178,10 @@
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
Integer.toString(userId), userId);
- mInjector.getSystemServiceManager().onUserStopping(userId, visibilityChanged);
+ mInjector.getSystemServiceManager().onUserStopping(userId);
+ if (visibilityChanged) {
+ mInjector.onUserVisibilityChanged(userId, /* visible= */ false);
+ }
Runnable finishUserStoppedAsync = () ->
mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1626,7 +1650,29 @@
return false;
}
- mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
+ t.traceBegin("assignUserToDisplayOnStart");
+ int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
+ userInfo.profileGroupId, foreground, displayId);
+ t.traceEnd();
+
+ boolean visible;
+ switch (result) {
+ case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
+ visible = true;
+ break;
+ case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
+ visible = false;
+ break;
+ default:
+ Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
+ // Fall through
+ case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
+ Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+ (foreground ? "fg" : "bg"), userId, displayId,
+ UserManagerInternal.userAssignmentResultToString(result));
+ return false;
+ }
+
// TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
@@ -1678,12 +1724,23 @@
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
boolean userSwitchUiEnabled;
+ // TODO(b/244333150): temporary state until the callback logic is moved to
+ // UserVisibilityManager
+ int previousCurrentUserId; boolean notifyPreviousCurrentUserId;
synchronized (mLock) {
+ previousCurrentUserId = mCurrentUserId;
+ notifyPreviousCurrentUserId = mVisibleUsers.get(previousCurrentUserId);
+ if (notifyPreviousCurrentUserId) {
+ deleteVisibleUserLocked(previousCurrentUserId);
+ }
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
userSwitchUiEnabled = mUserSwitchUiEnabled;
}
mInjector.updateUserConfiguration();
+ // TODO(b/244644281): updateProfileRelatedCaches() is called on both if and else
+ // parts, ideally it should be moved outside, but for now it's not as there are many
+ // calls to external components here afterwards
updateProfileRelatedCaches();
mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
@@ -1696,6 +1753,11 @@
mInjector.getWindowManager().lockNow(null);
}
}
+ if (notifyPreviousCurrentUserId) {
+ mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG,
+ previousCurrentUserId, 0));
+ }
+
} else {
final Integer currentUserIdInt = mCurrentUserId;
updateProfileRelatedCaches();
@@ -1706,25 +1768,9 @@
}
t.traceEnd();
- // Need to call UM when user is on background, as there are some cases where the user
- // cannot be started in background on a secondary display (for example, if user is a
- // profile).
- // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
- // the UM call would return true during boot (when CarService / BootUserInitializer
- // calls AM.startUserInBackground() because the system user is still the current user.
- // TODO(b/244644281): another fragility of this check is that it must wait to call
- // UMI.isUserVisible() until the user state is check, as that method checks if the
- // profile of the current user is started. We should fix that dependency so the logic
- // belongs to just one place (like UserDisplayAssigner)
- boolean visible = foreground
- || userId != UserHandle.USER_SYSTEM
- && mInjector.getUserManagerInternal().isUserVisible(userId);
if (visible) {
synchronized (mLock) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "Adding %d to mVisibleUsers", userId);
- }
- mVisibleUsers.put(userId, true);
+ addVisibleUserLocked(userId);
}
}
@@ -1767,12 +1813,15 @@
mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
visible ? 1 : 0));
t.traceEnd();
- } else if (visible) {
+ }
+
+ if (visible) {
// User was already running and became visible (for example, when switching to a
// user that was started in the background before), so it's necessary to explicitly
// notify the services (while when the user starts from BOOTING, USER_START_MSG
// takes care of that.
- mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBLE_MSG, userId, NO_ARG2));
+ mHandler.sendMessage(
+ mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
}
t.traceBegin("sendMessages");
@@ -2075,6 +2124,8 @@
}
private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
synchronized (mLock) {
Slogf.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
@@ -2084,6 +2135,7 @@
mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
}
+ t.traceEnd();
}
private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
@@ -2141,6 +2193,8 @@
+ " ms after dispatchUserSwitch.");
}
+ TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(TAG);
+ t2.traceBegin("onUserSwitchingReply-" + name);
curWaitingUserSwitchCallbacks.remove(name);
// Continue switching if all callbacks have been notified and
// user switching session is still valid
@@ -2149,11 +2203,15 @@
== mCurWaitingUserSwitchCallbacks)) {
sendContinueUserSwitchLU(uss, oldUserId, newUserId);
}
+ t2.traceEnd();
}
}
};
+ t.traceBegin("onUserSwitching-" + name);
mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
+ t.traceEnd();
} catch (RemoteException e) {
+ // Ignore
}
}
} else {
@@ -2167,10 +2225,13 @@
@GuardedBy("mLock")
private void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("sendContinueUserSwitchLU-" + oldUserId + "-to-" + newUserId);
mCurWaitingUserSwitchCallbacks = null;
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
oldUserId, newUserId, uss));
+ t.traceEnd();
}
@VisibleForTesting
@@ -2552,7 +2613,8 @@
if (!UserManager.isHeadlessSystemUserMode()) {
// Don't need to call on HSUM because it will be called when the system user is
// restarted on background
- mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true);
+ mInjector.onUserStarting(UserHandle.USER_SYSTEM);
+ mInjector.onUserVisibilityChanged(UserHandle.USER_SYSTEM, /* visible= */ true);
}
}
@@ -2564,12 +2626,12 @@
int userId = UserHandle.USER_SYSTEM;
synchronized (mLock) {
if (visible) {
- mVisibleUsers.put(userId, true);
+ addVisibleUserLocked(userId);
} else {
- mVisibleUsers.delete(userId);
+ deleteVisibleUserLocked(userId);
}
}
- mInjector.notifySystemUserVisibilityChanged(visible);
+ mInjector.onUserVisibilityChanged(userId, visible);
t.traceEnd();
}
@@ -3069,7 +3131,7 @@
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
- mInjector.onUserStarting(/* userId= */ msg.arg1, /* visible= */ msg.arg2 == 1);
+ mInjector.onUserStarting(/* userId= */ msg.arg1);
scheduleOnUserCompletedEvent(msg.arg1,
UserCompletedEventType.EVENT_TYPE_USER_STARTING,
USER_COMPLETED_EVENT_DELAY_MS);
@@ -3150,8 +3212,9 @@
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1);
break;
- case USER_VISIBLE_MSG:
- mInjector.getSystemServiceManager().onUserVisible(/* userId= */ msg.arg1);
+ case USER_VISIBILITY_CHANGED_MSG:
+ mInjector.onUserVisibilityChanged(/* userId= */ msg.arg1,
+ /* visible= */ msg.arg2 == 1);
break;
}
return false;
@@ -3653,20 +3716,28 @@
}
protected void dismissKeyguard(Runnable runnable, String reason) {
+ final AtomicBoolean isFirst = new AtomicBoolean(true);
+ final Runnable runOnce = () -> {
+ if (isFirst.getAndSet(false)) {
+ runnable.run();
+ }
+ };
+
+ mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS);
getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() {
@Override
public void onDismissError() throws RemoteException {
- mHandler.post(runnable);
+ mHandler.post(runOnce);
}
@Override
public void onDismissSucceeded() throws RemoteException {
- mHandler.post(runnable);
+ mHandler.post(runOnce);
}
@Override
public void onDismissCancelled() throws RemoteException {
- mHandler.post(runnable);
+ mHandler.post(runOnce);
}
}, reason);
}
@@ -3675,12 +3746,12 @@
return UserManager.isUsersOnSecondaryDisplaysEnabled();
}
- void onUserStarting(int userId, boolean visible) {
- getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
- visible);
+ void onUserStarting(@UserIdInt int userId) {
+ getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
- void notifySystemUserVisibilityChanged(boolean visible) {
- getSystemServiceManager().onSystemUserVisibilityChanged(visible);
+
+ void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ getUserManagerInternal().onUserVisibilityChanged(userId, visible);
}
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index efa2f25..64f2aa3 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -883,6 +883,7 @@
@Override
public void onUserStarting(@NonNull TargetUser user) {
+ Slog.d(TAG, "Starting user " + user.getUserIdentifier());
mService.onUserStarting(user,
Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
}
@@ -1047,6 +1048,8 @@
"com.android.server.app.GameManagerService");
if (!mSettings.containsKey(userId)) {
+ Slog.d(TAG, "Failed to set game mode for package " + packageName
+ + " as user " + userId + " is not started");
return;
}
GameManagerSettings userSettings = mSettings.get(userId);
@@ -1311,16 +1314,9 @@
void onUserSwitching(TargetUser from, TargetUser to) {
final int toUserId = to.getUserIdentifier();
- if (from != null) {
- synchronized (mLock) {
- final int fromUserId = from.getUserIdentifier();
- if (mSettings.containsKey(fromUserId)) {
- sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
- 0 /*delayMillis*/);
- }
- }
- }
-
+ // we want to re-populate the setting when switching user as the device config may have
+ // changed, which will only update for the previous user, see
+ // DeviceConfigListener#onPropertiesChanged.
sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
0 /*delayMillis*/);
@@ -1389,8 +1385,9 @@
Slog.v(TAG, "Package configuration not found for " + packageName);
return;
}
+ } else {
+ updateFps(packageConfig, packageName, gameMode, userId);
}
- updateFps(packageConfig, packageName, gameMode, userId);
updateUseAngle(packageName, gameMode);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a58583c..7e00c32 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2920,18 +2920,18 @@
}
@Override
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
- return mCheckOpsDelegateDispatcher.startProxyOperation(code, attributionSource,
+ return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
attributionChainId);
}
- private SyncNotedAppOp startProxyOperationImpl(int code,
+ private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
@@ -2940,11 +2940,9 @@
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final IBinder proxyToken = attributionSource.getToken();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
- final IBinder proxiedToken = attributionSource.getNextToken();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
@@ -2986,7 +2984,7 @@
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final SyncNotedAppOp testProxiedOp = startOperationUnchecked(proxiedToken, code,
+ final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
@@ -2998,7 +2996,7 @@
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final SyncNotedAppOp proxyAppOp = startOperationUnchecked(proxyToken, code, proxyUid,
+ final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId,
@@ -3008,7 +3006,7 @@
}
}
- return startOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
+ return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
@@ -3151,22 +3149,20 @@
}
@Override
- public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation) {
- mCheckOpsDelegateDispatcher.finishProxyOperation(code, attributionSource,
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
+ mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation);
}
- private Void finishProxyOperationImpl(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation) {
+ private Void finishProxyOperationImpl(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final IBinder proxyToken = attributionSource.getToken();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
- final IBinder proxiedToken = attributionSource.getNextToken();
skipProxyOperation = skipProxyOperation
&& isCallerAndAttributionTrusted(attributionSource);
@@ -3185,7 +3181,7 @@
}
if (!skipProxyOperation) {
- finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName,
+ finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
proxyAttributionTag);
}
@@ -3195,7 +3191,7 @@
return null;
}
- finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
+ finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag);
return null;
@@ -6262,7 +6258,6 @@
Objects.requireNonNull(stackTrace);
Preconditions.checkArgument(op >= 0);
Preconditions.checkArgument(op < AppOpsManager._NUM_OP);
- Objects.requireNonNull(version);
NoteOpTrace noteOpTrace = new NoteOpTrace(stackTrace, op, packageName, version);
@@ -6436,42 +6431,42 @@
attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
}
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
- return mPolicy.startProxyOperation(code, attributionSource,
+ return mPolicy.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId,
this::startDelegateProxyOperationImpl);
} else {
- return mPolicy.startProxyOperation(code, attributionSource,
+ return mPolicy.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId,
AppOpsService.this::startProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
- return startDelegateProxyOperationImpl(code, attributionSource,
+ return startDelegateProxyOperationImpl(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
}
- return startProxyOperationImpl(code, attributionSource, startIfModeDefault,
+ return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
- private SyncNotedAppOp startDelegateProxyOperationImpl(int code,
+ private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) {
- return mCheckOpsDelegate.startProxyOperation(code, attributionSource,
+ return mCheckOpsDelegate.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs,
attributionChainId, AppOpsService.this::startProxyOperationImpl);
@@ -6500,27 +6495,28 @@
AppOpsService.this::finishOperationImpl);
}
- public void finishProxyOperation(int code,
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
- mPolicy.finishProxyOperation(code, attributionSource,
+ mPolicy.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, this::finishDelegateProxyOperationImpl);
} else {
- mPolicy.finishProxyOperation(code, attributionSource,
+ mPolicy.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
- finishDelegateProxyOperationImpl(code, attributionSource, skipProxyOperation);
+ finishDelegateProxyOperationImpl(clientId, code, attributionSource,
+ skipProxyOperation);
} else {
- finishProxyOperationImpl(code, attributionSource, skipProxyOperation);
+ finishProxyOperationImpl(clientId, code, attributionSource, skipProxyOperation);
}
}
- private Void finishDelegateProxyOperationImpl(int code,
+ private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
- mCheckOpsDelegate.finishProxyOperation(code, attributionSource, skipProxyOperation,
- AppOpsService.this::finishProxyOperationImpl);
+ mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
+ skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
return null;
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 3c281d1..5114bd5 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -26,6 +26,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
@@ -171,6 +172,7 @@
return MODE_ALLOWED;
}
case OP_RECORD_AUDIO:
+ case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO:
if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) == 0) {
return MODE_IGNORED;
} else {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index fab7f1d..d971953 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -780,6 +780,8 @@
@Override // Binder call
public void resetLockout(
int userId, byte[] hardwareAuthToken) {
+ super.resetLockout_enforcePermission();
+
Slog.d(TAG, "resetLockout(userId=" + userId
+ ", hat=" + (hardwareAuthToken == null ? "null " : "present") + ")");
mBiometricContext.getAuthSessionCoordinator()
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 36acc3c..8b80674 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "CtsBiometricsTestCases"
+ },
+ {
+ "name": "CtsBiometricsHostTestCases"
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index da43618..d584c99 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -120,7 +120,7 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(false /* destroying */);
}
}
@@ -130,7 +130,7 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(true /* destroying */);
mDestroyed = true;
}
}
@@ -177,11 +177,10 @@
final float current = mLastAmbientLux;
if (current > -1f) {
nextConsumer.consume(current);
- } else if (mDestroyed) {
- nextConsumer.consume(-1f);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
} else {
+ mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
}
@@ -199,12 +198,14 @@
resetTimerLocked(true /* start */);
}
- private void disableLightSensorLoggingLocked() {
+ private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
if (mEnabled) {
mEnabled = false;
- mLastAmbientLux = -1;
+ if (!destroying) {
+ mLastAmbientLux = -1;
+ }
mSensorManager.unregisterListener(mLightSensorListener);
Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 7a13c91..d11f099 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.face.AuthenticationFrame;
import android.hardware.biometrics.face.BaseFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceEnrollFrame;
@@ -33,6 +34,7 @@
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.face.FaceUtils;
import java.util.HashSet;
@@ -200,22 +202,24 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
@Override
public void notifyAcquired(int userId, int acquireInfo) {
-
super.notifyAcquired_enforcePermission();
BaseFrame data = new BaseFrame();
data.acquiredInfo = (byte) acquireInfo;
- AuthenticationFrame authenticationFrame = new AuthenticationFrame();
- authenticationFrame.data = data;
-
- // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
- // This will need to call the correct callback once the onAcquired callback is removed.
- mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFrame(
- authenticationFrame);
+ if (mSensor.getScheduler().getCurrentClient() instanceof EnrollClient) {
+ final EnrollmentFrame frame = new EnrollmentFrame();
+ frame.data = data;
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
+ .onEnrollmentFrame(frame);
+ } else {
+ final AuthenticationFrame frame = new AuthenticationFrame();
+ frame.data = data;
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
+ .onAuthenticationFrame(frame);
+ }
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 0f5cdc3..0d30ddd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -637,7 +637,7 @@
public void onBinderDied() {
final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (client.isInterruptable()) {
+ if (client != null && client.isInterruptable()) {
Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
final ErrorConsumer errorConsumer = (ErrorConsumer) client;
errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 48367b2..1c57151 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -43,6 +43,7 @@
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -881,29 +882,27 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
- public void onPointerDown(long requestId, int sensorId, int x, int y,
- float minor, float major) {
+ public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
super.onPointerDown_enforcePermission();
-
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
return;
}
- provider.onPointerDown(requestId, sensorId, x, y, minor, major);
+ provider.onPointerDown(requestId, sensorId, pc);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
- public void onPointerUp(long requestId, int sensorId) {
- super.onPointerUp_enforcePermission();
+ public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
+ super.onPointerUp_enforcePermission();
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
return;
}
- provider.onPointerUp(requestId, sensorId);
+ provider.onPointerUp(requestId, sensorId, pc);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@@ -942,6 +941,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ super.setUdfpsOverlay_enforcePermission();
+
for (ServiceProvider provider : mRegistry.getProviders()) {
provider.setUdfpsOverlay(controller);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 05c2e29..5b6f14d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -21,6 +21,7 @@
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -122,9 +123,9 @@
@NonNull IInvalidationCallback callback);
- void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
+ void onPointerDown(long requestId, int sensorId, PointerContext pc);
- void onPointerUp(long requestId, int sensorId);
+ void onPointerUp(long requestId, int sensorId, PointerContext pc);
void onUiReady(long requestId, int sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index a2c0751..da7163a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint;
+import android.hardware.biometrics.fingerprint.PointerContext;
+
import com.android.server.biometrics.sensors.BaseClientMonitor;
/**
@@ -24,8 +26,8 @@
* finger position (e.g. enroll, authenticate) should implement this.
*/
public interface Udfps {
- void onPointerDown(int x, int y, float minor, float major);
- void onPointerUp();
+ void onPointerDown(PointerContext pc);
+ void onPointerUp(PointerContext pc);
void onUiReady();
boolean isPointerDown();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 5f38a92..11f4517 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -94,6 +94,7 @@
private long mSideFpsLastAcquireStartTime;
private Runnable mAuthSuccessRunnable;
private final Clock mClock;
+ private boolean mDidFinishSfps;
FingerprintAuthenticationClient(
@NonNull Context context,
@@ -203,8 +204,9 @@
@Override
protected void handleLifecycleAfterAuth(boolean authenticated) {
- if (authenticated) {
+ if (authenticated && !mDidFinishSfps) {
mCallback.onClientFinished(this, true /* success */);
+ mDidFinishSfps = true;
}
}
@@ -381,23 +383,17 @@
}
@Override
- public void onPointerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(PointerContext pc) {
try {
mIsPointerDown = true;
mState = STATE_STARTED;
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final PointerContext context = new PointerContext();
- context.pointerId = 0;
- context.x = x;
- context.y = y;
- context.minor = minor;
- context.major = major;
- context.isAod = getBiometricContext().isAod();
- session.getSession().onPointerDownWithContext(context);
+ session.getSession().onPointerDownWithContext(pc);
} else {
- session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+ session.getSession().onPointerDown(pc.pointerId, (int) pc.x, (int) pc.y, pc.minor,
+ pc.major);
}
if (getListener() != null) {
@@ -409,18 +405,16 @@
}
@Override
- public void onPointerUp() {
+ public void onPointerUp(PointerContext pc) {
try {
mIsPointerDown = false;
mState = STATE_STARTED_PAUSED_ATTEMPTED;
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final PointerContext context = new PointerContext();
- context.pointerId = 0;
- session.getSession().onPointerUpWithContext(context);
+ session.getSession().onPointerUpWithContext(pc);
} else {
- session.getSession().onPointerUp(0 /* pointerId */);
+ session.getSession().onPointerUp(pc.pointerId);
}
if (getListener() != null) {
@@ -504,12 +498,16 @@
if (mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): onPowerPressed");
mHandler.post(() -> {
+ if (mDidFinishSfps) {
+ return;
+ }
Slog.i(TAG, "(sideFPS): finishing auth");
// Ignore auths after a power has been detected
mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
// Do not call onError() as that will send an additional callback to coex.
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
- 0, true);
+ mDidFinishSfps = true;
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ stopHalOperation();
mSensorOverlays.hide(getSensorId());
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 7e5d39f..fa54983 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -219,22 +219,16 @@
}
@Override
- public void onPointerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(PointerContext pc) {
try {
mIsPointerDown = true;
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final PointerContext context = new PointerContext();
- context.pointerId = 0;
- context.x = x;
- context.y = y;
- context.minor = minor;
- context.major = major;
- context.isAod = getBiometricContext().isAod();
- session.getSession().onPointerDownWithContext(context);
+ session.getSession().onPointerDownWithContext(pc);
} else {
- session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+ session.getSession().onPointerDown(pc.pointerId, (int) pc.x, (int) pc.y, pc.minor,
+ pc.major);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer down", e);
@@ -242,17 +236,15 @@
}
@Override
- public void onPointerUp() {
+ public void onPointerUp(PointerContext pc) {
try {
mIsPointerDown = false;
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
- final PointerContext context = new PointerContext();
- context.pointerId = 0;
- session.getSession().onPointerUpWithContext(context);
+ session.getSession().onPointerUpWithContext(pc);
} else {
- session.getSession().onPointerUp(0 /* pointerId */);
+ session.getSession().onPointerUp(pc.pointerId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer up", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index b42b1c6..776d331 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -34,6 +34,7 @@
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -627,25 +628,24 @@
}
@Override
- public void onPointerDown(long requestId, int sensorId, int x, int y,
- float minor, float major) {
+ public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
if (!(client instanceof Udfps)) {
Slog.e(getTag(), "onPointerDown received during client: " + client);
return;
}
- ((Udfps) client).onPointerDown(x, y, minor, major);
+ ((Udfps) client).onPointerDown(pc);
});
}
@Override
- public void onPointerUp(long requestId, int sensorId) {
+ public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
if (!(client instanceof Udfps)) {
Slog.e(getTag(), "onPointerUp received during client: " + client);
return;
}
- ((Udfps) client).onPointerUp();
+ ((Udfps) client).onPointerUp(pc);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 1f30363..4567addc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -30,6 +30,7 @@
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
@@ -807,25 +808,24 @@
}
@Override
- public void onPointerDown(long requestId, int sensorId, int x, int y,
- float minor, float major) {
+ public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
if (!(client instanceof Udfps)) {
Slog.w(TAG, "onFingerDown received during client: " + client);
return;
}
- ((Udfps) client).onPointerDown(x, y, minor, major);
+ ((Udfps) client).onPointerDown(pc);
});
}
@Override
- public void onPointerUp(long requestId, int sensorId) {
+ public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
if (!(client instanceof Udfps)) {
Slog.w(TAG, "onFingerDown received during client: " + client);
return;
}
- ((Udfps) client).onPointerUp();
+ ((Udfps) client).onPointerUp(pc);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index bea0f4f..34c6265 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -21,6 +21,7 @@
import android.app.trust.TrustManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -441,8 +442,7 @@
}
@Override
- public void onPointerDown(long requestId, int sensorId, int x, int y, float minor,
- float major) {
+ public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
mHandler.post(() -> {
Slog.d(TAG, "onFingerDown");
final AuthenticationConsumer lastAuthenticatedConsumer =
@@ -489,7 +489,7 @@
}
@Override
- public void onPointerUp(long requestId, int sensorId) {
+ public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
mHandler.post(() -> {
Slog.d(TAG, "onFingerUp");
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 089317e..a9cc897 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -24,6 +24,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
@@ -234,11 +235,11 @@
}
@Override
- public void onPointerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(PointerContext pc) {
mIsPointerDown = true;
mState = STATE_STARTED;
mALSProbeCallback.getProbe().enable();
- UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+ UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
if (getListener() != null) {
try {
@@ -250,7 +251,7 @@
}
@Override
- public void onPointerUp() {
+ public void onPointerUp(PointerContext pc) {
mIsPointerDown = false;
mState = STATE_STARTED_PAUSED_ATTEMPTED;
mALSProbeCallback.getProbe().disable();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 3e9b8ef..cfa9fb4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -110,13 +111,13 @@
}
@Override
- public void onPointerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(PointerContext pc) {
mIsPointerDown = true;
- UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+ UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
}
@Override
- public void onPointerUp() {
+ public void onPointerUp(PointerContext pc) {
mIsPointerDown = false;
UdfpsHelper.onFingerUp(getFreshDaemon());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 3371cec..78039ef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -168,13 +169,13 @@
}
@Override
- public void onPointerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(PointerContext pc) {
mIsPointerDown = true;
- UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+ UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
}
@Override
- public void onPointerUp() {
+ public void onPointerUp(PointerContext pc) {
mIsPointerDown = false;
UdfpsHelper.onFingerUp(getFreshDaemon());
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 71ba296..4fcfea2 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -169,7 +169,7 @@
synchronized (mLock) {
List<RadioManager.ModuleProperties> moduleList = new ArrayList<>(mModules.size());
for (int i = 0; i < mModules.size(); i++) {
- moduleList.add(mModules.valueAt(i).mProperties);
+ moduleList.add(mModules.valueAt(i).getProperties());
}
return moduleList;
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index c6dc431..d4c7242 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -54,11 +54,11 @@
private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
private final IBroadcastRadio mService;
- public final RadioManager.ModuleProperties mProperties;
private final Object mLock;
private final Handler mHandler;
private final RadioLogger mLogger;
+ private final RadioManager.ModuleProperties mProperties;
/**
* Tracks antenna state reported by HAL (if any).
@@ -217,6 +217,10 @@
return mService;
}
+ public RadioManager.ModuleProperties getProperties() {
+ return mProperties;
+ }
+
void setInternalHalCallback() throws RemoteException {
synchronized (mLock) {
mService.setTunerCallback(mHalTunerCallback);
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 5605737..4c37609 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -33,6 +33,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Collection;
import java.util.HashMap;
@@ -132,10 +133,22 @@
}
}
+ @VisibleForTesting
+ BroadcastRadioService(int nextModuleId, Object lock, IServiceManager manager) {
+ mNextModuleId = nextModuleId;
+ mLock = lock;
+ Objects.requireNonNull(manager, "Service manager cannot be null");
+ try {
+ manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to register for service notifications: ", ex);
+ }
+ }
+
public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
Slog.v(TAG, "List HIDL 2.0 modules");
synchronized (mLock) {
- return mModules.values().stream().map(module -> module.mProperties)
+ return mModules.values().stream().map(module -> module.getProperties())
.collect(Collectors.toList());
}
}
@@ -154,7 +167,7 @@
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
- Slog.v(TAG, "Open HIDL 2.0 session");
+ Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
Objects.requireNonNull(callback);
if (!withAudio) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 726cdc3..3daf1db 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -52,8 +52,13 @@
import java.util.stream.Collectors;
class Convert {
+
private static final String TAG = "BcRadio2Srv.convert";
+ private Convert() {
+ throw new UnsupportedOperationException("Convert class is noninstantiable");
+ }
+
static void throwOnError(String action, int result) {
switch (result) {
case Result.OK:
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
index a9d8054..a6cf72c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
@@ -34,13 +34,4 @@
public Mutable() {
value = null;
}
-
- /**
- * Initialize value with specific value.
- *
- * @param value initial value.
- */
- public Mutable(E value) {
- this.value = value;
- }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 0a23e38..5913e068 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -58,7 +58,7 @@
private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
@NonNull private final IBroadcastRadio mService;
- @NonNull public final RadioManager.ModuleProperties mProperties;
+ @NonNull private final RadioManager.ModuleProperties mProperties;
private final Object mLock;
@NonNull private final Handler mHandler;
@@ -177,6 +177,10 @@
return mService;
}
+ public RadioManager.ModuleProperties getProperties() {
+ return mProperties;
+ }
+
public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
throws RemoteException {
mEventLogger.logRadioEvent("Open TunerSession");
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
index 384c9ba..188c25d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
@@ -25,9 +25,14 @@
AM_LW,
AM_MW,
AM_SW,
-};
+}
class Utils {
+
+ private Utils() {
+ throw new UnsupportedOperationException("Utils class is noninstantiable");
+ }
+
private static final String TAG = "BcRadio2Srv.utils";
static FrequencyBand getBand(int freq) {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 81b56a3..d2e572f 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
import java.util.Set;
@@ -109,4 +110,14 @@
* Returns true if the {@code displayId} is owned by any virtual device
*/
public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
+
+ /**
+ * Returns the device policy for the given virtual device and policy type.
+ *
+ * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+ * policy for that device and policy type, then
+ * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+ */
+ public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+ int deviceId, @VirtualDeviceParams.PolicyType int policyType);
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6a000d9..bc9bc03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -127,6 +127,8 @@
import android.system.keystore2.KeyPermission;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
import android.util.Log;
import android.util.Range;
@@ -296,6 +298,10 @@
return mVpnProfileStore;
}
+ private static final int MAX_EVENTS_LOGS = 20;
+ private final LocalLog mUnderlyNetworkChanges = new LocalLog(MAX_EVENTS_LOGS);
+ private final LocalLog mVpnManagerEvents = new LocalLog(MAX_EVENTS_LOGS);
+
/**
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
* only applies to {@link VpnService} connections.
@@ -639,7 +645,8 @@
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
- .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+ .setTransportInfo(new VpnTransportInfo(
+ VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
.build();
loadAlwaysOnPackage();
@@ -703,7 +710,8 @@
private void resetNetworkCapabilities() {
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUids(null)
- .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+ .setTransportInfo(new VpnTransportInfo(
+ VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
.build();
}
@@ -841,6 +849,9 @@
int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
@NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
@Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+ mVpnManagerEvents.log("Event class=" + getVpnManagerEventClassName(errorClass)
+ + ", err=" + getVpnManagerEventErrorName(errorCode) + " for " + packageName
+ + " on session " + sessionKey);
final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
return sendEventToVpnManagerApp(intent, packageName);
@@ -1558,7 +1569,8 @@
capsBuilder.setUids(createUserAndRestrictedProfilesRanges(mUserId,
mConfig.allowedApplications, mConfig.disallowedApplications));
- capsBuilder.setTransportInfo(new VpnTransportInfo(getActiveVpnType(), mConfig.session));
+ capsBuilder.setTransportInfo(
+ new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
// Only apps targeting Q and above can explicitly declare themselves as metered.
// These VPNs are assumed metered unless they state otherwise.
@@ -1572,6 +1584,7 @@
? Arrays.asList(mConfig.underlyingNetworks) : null);
mNetworkCapabilities = capsBuilder.build();
+ logUnderlyNetworkChanges(mNetworkCapabilities.getUnderlyingNetworks());
mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
mNetworkCapabilities, lp,
new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
@@ -1599,6 +1612,11 @@
}
}
+ private void logUnderlyNetworkChanges(List<Network> networks) {
+ mUnderlyNetworkChanges.log("Switch to "
+ + ((networks != null) ? TextUtils.join(", ", networks) : "null"));
+ }
+
private void agentDisconnect(NetworkAgent networkAgent) {
if (networkAgent != null) {
networkAgent.unregister();
@@ -4372,6 +4390,7 @@
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
// ConnectivityServiceTest.
if (SdkLevel.isAtLeastT()) {
+ mVpnManagerEvents.log(packageName + " stopped");
sendEventToVpnManagerApp(intent, packageName);
}
}
@@ -4539,8 +4558,10 @@
/** Proxy to allow different testing setups */
// TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
// NetworkAgent#setUnderlyingNetworks can be un-finalized.
- private static void doSetUnderlyingNetworks(
+ private void doSetUnderlyingNetworks(
@NonNull NetworkAgent agent, @NonNull List<Network> networks) {
+ logUnderlyNetworkChanges(networks);
+
if (agent instanceof VpnNetworkAgentWrapper) {
((VpnNetworkAgentWrapper) agent).doSetUnderlyingNetworks(networks);
} else {
@@ -4659,4 +4680,57 @@
static Range<Integer> createUidRangeForUser(int userId) {
return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
}
+
+ private String getVpnManagerEventClassName(int code) {
+ switch (code) {
+ case VpnManager.ERROR_CLASS_NOT_RECOVERABLE:
+ return "ERROR_CLASS_NOT_RECOVERABLE";
+ case VpnManager.ERROR_CLASS_RECOVERABLE:
+ return "ERROR_CLASS_RECOVERABLE";
+ default:
+ return "UNKNOWN_CLASS";
+ }
+ }
+
+ private String getVpnManagerEventErrorName(int code) {
+ switch (code) {
+ case VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST:
+ return "ERROR_CODE_NETWORK_UNKNOWN_HOST";
+ case VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT:
+ return "ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT";
+ case VpnManager.ERROR_CODE_NETWORK_IO:
+ return "ERROR_CODE_NETWORK_IO";
+ case VpnManager.ERROR_CODE_NETWORK_LOST:
+ return "ERROR_CODE_NETWORK_LOST";
+ default:
+ return "UNKNOWN_ERROR";
+ }
+ }
+
+ /** Dumps VPN state. */
+ public void dump(IndentingPrintWriter pw) {
+ synchronized (Vpn.this) {
+ pw.println("Active package name: " + mPackage);
+ pw.println("Active vpn type: " + getActiveVpnType());
+ pw.println("NetworkCapabilities: " + mNetworkCapabilities);
+ if (isIkev2VpnRunner()) {
+ final IkeV2VpnRunner runner = ((IkeV2VpnRunner) mVpnRunner);
+ pw.println("Token: " + runner.mSessionKey);
+ pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
+ if (mDataStallSuspected) pw.println("Data stall suspected");
+ if (runner.mScheduledHandleDataStallFuture != null) {
+ pw.println("Reset session scheduled");
+ }
+ }
+ pw.println("mUnderlyNetworkChanges (most recent first):");
+ pw.increaseIndent();
+ mUnderlyNetworkChanges.reverseDump(pw);
+ pw.decreaseIndent();
+
+ pw.println("mVpnManagerEvent (most recent first):");
+ pw.increaseIndent();
+ mVpnManagerEvents.reverseDump(pw);
+ pw.decreaseIndent();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
index ffaf364..108cddc 100644
--- a/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
+++ b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
@@ -17,8 +17,8 @@
import android.app.ActivityManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.pm.UserPackage;
import android.os.SystemClock;
-import android.util.Pair;
import com.android.server.LocalServices;
@@ -26,8 +26,7 @@
class SyncAdapterStateFetcher {
- private final HashMap<Pair<Integer, String>, Integer> mBucketCache =
- new HashMap<>();
+ private final HashMap<UserPackage, Integer> mBucketCache = new HashMap<>();
public SyncAdapterStateFetcher() {
}
@@ -36,7 +35,7 @@
* Return sync adapter state with a cache.
*/
public int getStandbyBucket(int userId, String packageName) {
- final Pair<Integer, String> key = Pair.create(userId, packageName);
+ final UserPackage key = UserPackage.of(userId, packageName);
final Integer cached = mBucketCache.get(key);
if (cached != null) {
return cached;
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 73afa60..eb81e70 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2215,7 +2215,8 @@
pw.print("Storage low: "); pw.println(storageLowIntent != null);
pw.print("Clock valid: "); pw.println(mSyncStorageEngine.isClockValid());
- final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
+ final AccountAndUser[] accounts =
+ AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
pw.print("Accounts: ");
if (accounts != INITIAL_ACCOUNTS_ARRAY) {
@@ -3274,7 +3275,8 @@
private void updateRunningAccountsH(EndPoint syncTargets) {
synchronized (mAccountsLock) {
AccountAndUser[] oldAccounts = mRunningAccounts;
- mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+ mRunningAccounts =
+ AccountManagerService.getSingleton().getRunningAccountsForSystem();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "Accounts list: ");
for (AccountAndUser acc : mRunningAccounts) {
@@ -3316,7 +3318,8 @@
}
// Cancel all jobs from non-existent accounts.
- AccountAndUser[] allAccounts = AccountManagerService.getSingleton().getAllAccounts();
+ AccountAndUser[] allAccounts =
+ AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
List<SyncOperation> ops = getAllPendingSyncs();
for (int i = 0, opsSize = ops.size(); i < opsSize; i++) {
SyncOperation op = ops.get(i);
diff --git a/services/core/java/com/android/server/cpu/OWNERS b/services/core/java/com/android/server/cpu/OWNERS
new file mode 100644
index 0000000..2f42363
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 608533
+
+include platform/packages/services/Car:/OWNERS
+lakshmana@google.com
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index f8d4b8f..42fe9d8 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -18,11 +18,11 @@
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceStateManager;
import com.android.internal.util.Preconditions;
@@ -55,6 +55,15 @@
*/
public static final int FLAG_APP_INACCESSIBLE = 1 << 1;
+ /**
+ * Some device states can be both entered through a physical configuration as well as emulation
+ * through {@link DeviceStateManager#requestState}, while some states can only be entered
+ * through emulation and have no physical configuration to match.
+ *
+ * This flag indicates that the corresponding state can only be entered through emulation.
+ */
+ public static final int FLAG_EMULATED_ONLY = 1 << 2;
+
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
FLAG_CANCEL_OVERRIDE_REQUESTS,
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 44c8e18..925fc21 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,11 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceStateManager.ACTION_SHOW_REAR_DISPLAY_OVERLAY;
+import static android.hardware.devicestate.DeviceStateManager.EXTRA_ORIGINAL_DEVICE_BASE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -31,7 +36,10 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.Intent;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerInternal;
@@ -157,6 +165,15 @@
private Set<Integer> mDeviceStatesAvailableForAppRequests;
+ private Set<Integer> mFoldedDeviceStates;
+
+ @Nullable
+ private DeviceState mRearDisplayState;
+
+ // TODO(259328837) Generalize for all pending feature requests in the future
+ @Nullable
+ private OverrideRequest mRearDisplayPendingOverrideRequest;
+
@VisibleForTesting
interface SystemPropertySetter {
void setDebugTracingDeviceStateProperty(String value);
@@ -201,6 +218,7 @@
synchronized (mLock) {
readStatesAvailableForRequestFromApps();
+ mFoldedDeviceStates = readFoldedStates();
}
}
@@ -350,6 +368,8 @@
mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
updatePendingStateLocked();
+ setRearDisplayStateLocked();
+
if (!mPendingState.isPresent()) {
// If the change in the supported states didn't result in a change of the pending
// state commitPendingState() will never be called and the callbacks will never be
@@ -361,6 +381,15 @@
}
}
+ @GuardedBy("mLock")
+ private void setRearDisplayStateLocked() {
+ int rearDisplayIdentifier = getContext().getResources().getInteger(
+ R.integer.config_deviceStateRearDisplay);
+ if (rearDisplayIdentifier != INVALID_DEVICE_STATE) {
+ mRearDisplayState = mDeviceStates.get(rearDisplayIdentifier);
+ }
+ }
+
/**
* Returns {@code true} if the provided state is supported. Requires that
* {@link #mDeviceStates} is sorted prior to calling.
@@ -398,6 +427,10 @@
// Base state hasn't changed. Nothing to do.
return;
}
+ // There is a pending rear display request, so we check if the overlay should be closed
+ if (mRearDisplayPendingOverrideRequest != null) {
+ handleRearDisplayBaseStateChangedLocked(identifier);
+ }
mBaseState = Optional.of(baseState);
if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
@@ -663,7 +696,7 @@
}
private void requestStateInternal(int state, int flags, int callingPid,
- @NonNull IBinder token) {
+ @NonNull IBinder token, boolean hasControlDeviceStatePermission) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
@@ -685,10 +718,34 @@
OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
- mOverrideRequestController.addRequest(request);
+
+ // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
+ if (!hasControlDeviceStatePermission && mRearDisplayState != null
+ && state == mRearDisplayState.getIdentifier()) {
+ showRearDisplayEducationalOverlayLocked(request);
+ } else {
+ mOverrideRequestController.addRequest(request);
+ }
}
}
+ /**
+ * If we get a request to enter rear display mode, we need to display an educational
+ * overlay to let the user know what will happen. This creates the pending request and then
+ * launches the {@link RearDisplayEducationActivity}
+ */
+ @GuardedBy("mLock")
+ private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) {
+ mRearDisplayPendingOverrideRequest = request;
+
+ Intent intent = new Intent(ACTION_SHOW_REAR_DISPLAY_OVERLAY);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_ORIGINAL_DEVICE_BASE_STATE, mBaseState.get().getIdentifier());
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ getUiContext().startActivity(intent, options.toBundle());
+ }
+
private void cancelStateRequestInternal(int callingPid) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -738,6 +795,27 @@
}
}
+ /**
+ * Adds the rear display state request to the {@link OverrideRequestController} if the
+ * educational overlay was closed in a way that should enable the feature, and cancels the
+ * request if it was dismissed in a way that should cancel the feature.
+ */
+ private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) {
+ if (mRearDisplayPendingOverrideRequest != null) {
+ synchronized (mLock) {
+ if (shouldCancelRequest) {
+ ProcessRecord processRecord = mProcessRecords.get(
+ mRearDisplayPendingOverrideRequest.getPid());
+ processRecord.notifyRequestCanceledAsync(
+ mRearDisplayPendingOverrideRequest.getToken());
+ } else {
+ mOverrideRequestController.addRequest(mRearDisplayPendingOverrideRequest);
+ }
+ mRearDisplayPendingOverrideRequest = null;
+ }
+ }
+ }
+
private void dumpInternal(PrintWriter pw) {
pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
@@ -823,6 +901,16 @@
}
}
+ private Set<Integer> readFoldedStates() {
+ Set<Integer> foldedStates = new HashSet();
+ int[] mFoldedStatesArray = getContext().getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ for (int i = 0; i < mFoldedStatesArray.length; i++) {
+ foldedStates.add(mFoldedStatesArray[i]);
+ }
+ return foldedStates;
+ }
+
@GuardedBy("mLock")
private boolean isValidState(int state) {
for (int i = 0; i < mDeviceStates.size(); i++) {
@@ -833,6 +921,28 @@
return false;
}
+ /**
+ * If the device is being opened, in response to the rear display educational overlay, we should
+ * dismiss the overlay and enter the mode.
+ */
+ @GuardedBy("mLock")
+ private void handleRearDisplayBaseStateChangedLocked(int newBaseState) {
+ if (isDeviceOpeningLocked(newBaseState)) {
+ onStateRequestOverlayDismissedInternal(false);
+ }
+ }
+
+ /**
+ * Determines if the device is being opened and if we are going from a folded state to a
+ * non-folded state.
+ */
+ @GuardedBy("mLock")
+ private boolean isDeviceOpeningLocked(int newBaseState) {
+ return mBaseState.filter(
+ deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier())
+ && !mFoldedDeviceStates.contains(newBaseState)).isPresent();
+ }
+
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
@@ -850,6 +960,7 @@
if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
+
mCurrentBaseState = identifier;
setBaseState(identifier);
}
@@ -977,9 +1088,12 @@
throw new IllegalArgumentException("Request token must not be null.");
}
+ boolean hasControlStatePermission = getContext().checkCallingOrSelfPermission(
+ CONTROL_DEVICE_STATE) == PERMISSION_GRANTED;
+
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestStateInternal(state, flags, callingPid, token);
+ requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -1034,6 +1148,21 @@
}
@Override // Binder call
+ public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "CONTROL_DEVICE_STATE permission required to control the state request "
+ + "overlay");
+
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ onStateRequestOverlayDismissedInternal(shouldCancelRequest);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e907ebf..78b697d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1716,7 +1716,20 @@
final Point userPreferredResolution =
mPersistentDataStore.getUserPreferredResolution(device);
final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device);
- if (userPreferredResolution == null && Float.isNaN(refreshRate)) {
+ // If value in persistentDataStore is null, preserving the mode from systemPreferredMode.
+ // This is required because in some devices, user-preferred mode was not stored in
+ // persistentDataStore, but was stored in a config which is returned through
+ // systemPreferredMode.
+ if ((userPreferredResolution == null && Float.isNaN(refreshRate))
+ || (userPreferredResolution.equals(0, 0) && refreshRate == 0.0f)) {
+ Display.Mode systemPreferredMode = device.getSystemPreferredDisplayModeLocked();
+ if (systemPreferredMode == null) {
+ return;
+ }
+ storeModeInPersistentDataStoreLocked(
+ display.getDisplayIdLocked(), systemPreferredMode.getPhysicalWidth(),
+ systemPreferredMode.getPhysicalHeight(), systemPreferredMode.getRefreshRate());
+ device.setUserPreferredDisplayModeLocked(systemPreferredMode);
return;
}
Display.Mode.Builder modeBuilder = new Display.Mode.Builder();
@@ -1902,8 +1915,9 @@
if (displayDevice == null) {
return;
}
- if (mLogicalDisplayMapper.getDisplayLocked(displayDevice)
- .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) {
+ if (mLogicalDisplayMapper.getDisplayLocked(displayDevice) != null
+ && mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+ .getDisplayInfoLocked().type == Display.TYPE_INTERNAL && c != null) {
FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
c.getCurve().first,
c.getCurve().second,
@@ -2620,7 +2634,8 @@
// initPowerManagement has not yet been called.
return;
}
- if (mBrightnessTracker == null) {
+
+ if (mBrightnessTracker == null && display.getDisplayIdLocked() == Display.DEFAULT_DISPLAY) {
mBrightnessTracker = new BrightnessTracker(mContext, null);
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index a5e5c24..306b8cf 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -671,8 +671,10 @@
* changed
*/
public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
- mSettingsObserver.setRefreshRates(displayDeviceConfig);
- mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig);
+ mSettingsObserver.setRefreshRates(displayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ true);
+ mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ true);
}
/**
@@ -1322,19 +1324,25 @@
SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
super(handler);
mContext = context;
- setRefreshRates(/* displayDeviceConfig= */ null);
+ // We don't want to load from the DeviceConfig while constructing since this leads to
+ // a spike in the latency of DisplayManagerService startup. This happens because
+ // reading from the DeviceConfig is an intensive IO operation and having it in the
+ // startup phase where we thrive to keep the latency very low has significant impact.
+ setRefreshRates(/* displayDeviceConfig= */ null,
+ /* attemptLoadingFromDeviceConfig= */ false);
}
/**
* This is used to update the refresh rate configs from the DeviceConfig, which
* if missing from DisplayDeviceConfig, and finally fallback to config.xml.
*/
- public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig) {
- setDefaultPeakRefreshRate(displayDeviceConfig);
+ public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
mDefaultRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRate)
- : (float) displayDeviceConfig.getDefaultRefreshRate();
+ R.integer.config_defaultRefreshRate)
+ : (float) displayDeviceConfig.getDefaultRefreshRate();
}
public void observe() {
@@ -1395,13 +1403,27 @@
}
}
- private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig) {
+ @VisibleForTesting
+ float getDefaultRefreshRate() {
+ return mDefaultRefreshRate;
+ }
+
+ @VisibleForTesting
+ float getDefaultPeakRefreshRate() {
+ return mDefaultPeakRefreshRate;
+ }
+
+ private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
Float defaultPeakRefreshRate = null;
- try {
- defaultPeakRefreshRate =
+
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ defaultPeakRefreshRate =
mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
- } catch (Exception exception) {
- // Do nothing
+ } catch (Exception exception) {
+ // Do nothing
+ }
}
if (defaultPeakRefreshRate == null) {
defaultPeakRefreshRate =
@@ -1727,7 +1749,8 @@
mContext = context;
mHandler = handler;
mInjector = injector;
- updateBlockingZoneThresholds(/* displayDeviceConfig= */ null);
+ updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
+ /* attemptLoadingFromDeviceConfig= */ false);
mRefreshRateInHighZone = context.getResources().getInteger(
R.integer.config_fixedRefreshRateInHighZone);
}
@@ -1736,22 +1759,44 @@
* This is used to update the blocking zone thresholds from the DeviceConfig, which
* if missing from DisplayDeviceConfig, and finally fallback to config.xml.
*/
- public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig) {
- loadLowBrightnessThresholds(displayDeviceConfig);
- loadHighBrightnessThresholds(displayDeviceConfig);
+ public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
}
- private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+ @VisibleForTesting
+ int[] getLowDisplayBrightnessThreshold() {
+ return mLowDisplayBrightnessThresholds;
+ }
+
+ @VisibleForTesting
+ int[] getLowAmbientBrightnessThreshold() {
+ return mLowAmbientBrightnessThresholds;
+ }
+
+ @VisibleForTesting
+ int[] getHighDisplayBrightnessThreshold() {
+ return mHighDisplayBrightnessThresholds;
+ }
+
+ @VisibleForTesting
+ int[] getHighAmbientBrightnessThreshold() {
+ return mHighAmbientBrightnessThresholds;
+ }
+
+ private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
R.array.config_brightnessThresholdsOfPeakRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
R.array.config_ambientThresholdsOfPeakRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
throw new RuntimeException("display low brightness threshold array and ambient "
+ "brightness threshold array have different length: "
@@ -1762,17 +1807,18 @@
}
}
- private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+ private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
if (mHighDisplayBrightnessThresholds.length
!= mHighAmbientBrightnessThresholds.length) {
throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1788,13 +1834,16 @@
Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
Callable<int[]> loadFromDisplayDeviceConfigCallable,
int brightnessThresholdOfFixedRefreshRateKey,
- DisplayDeviceConfig displayDeviceConfig) {
+ DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
int[] brightnessThresholds = null;
- try {
- brightnessThresholds =
+
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ brightnessThresholds =
loadFromDeviceConfigDisplaySettingsCallable.call();
- } catch (Exception exception) {
- // Do nothing
+ } catch (Exception exception) {
+ // Do nothing
+ }
}
if (brightnessThresholds == null) {
try {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1f643d7..d6f0fd0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -927,7 +927,7 @@
// Initialize all of the brightness tracking state
final float brightness = convertToNits(mPowerState.getScreenBrightness());
- if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+ if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
mBrightnessSettingListener = brightnessValue -> {
@@ -1059,7 +1059,9 @@
}
loadAmbientLightSensor();
- if (mBrightnessTracker != null) {
+ // BrightnessTracker should only use one light sensor, we want to use the light sensor
+ // from the default display and not e.g. temporary displays when switching layouts.
+ if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
mBrightnessTracker.setLightSensor(mLightSensor);
}
@@ -1701,6 +1703,7 @@
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+ mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1711,12 +1714,6 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setFastAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
- mTempBrightnessEvent.setSlowAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2485,7 +2482,7 @@
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
- && mAutomaticBrightnessController != null) {
+ && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
// values into a physical brightness unit since the value provided by the API is in
// nits and not using the arbitrary backlight units.
@@ -2844,9 +2841,9 @@
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
convertToNits(event.getInitialBrightness()),
convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
+ event.getLux(),
event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
+ event.wasShortTermModelActive(),
appliedLowPowerMode,
appliedRbcStrength,
appliedHbmMaxNits,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3c1bf0b..990569c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -26,8 +26,6 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
@@ -54,7 +52,6 @@
import android.util.MutableFloat;
import android.util.MutableInt;
import android.util.Slog;
-import android.util.TimeUtils;
import android.view.Display;
import com.android.internal.R;
@@ -71,6 +68,7 @@
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.DisplayBrightnessController;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.utils.SensorUtils;
@@ -109,7 +107,7 @@
private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
private static final boolean DEBUG = false;
- private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+
// If true, uses the color fade on animation.
// We might want to turn this off if we cannot get a guarantee that the screen
@@ -123,31 +121,19 @@
private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
private static final int MSG_UPDATE_POWER_STATE = 1;
- private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
- private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
- private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
- private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
- private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
- private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
- private static final int MSG_IGNORE_PROXIMITY = 8;
- private static final int MSG_STOP = 9;
- private static final int MSG_UPDATE_BRIGHTNESS = 10;
- private static final int MSG_UPDATE_RBC = 11;
- private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
- private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
-
- private static final int PROXIMITY_UNKNOWN = -1;
- private static final int PROXIMITY_NEGATIVE = 0;
- private static final int PROXIMITY_POSITIVE = 1;
-
- // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
- private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
- private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+ private static final int MSG_SCREEN_ON_UNBLOCKED = 2;
+ private static final int MSG_SCREEN_OFF_UNBLOCKED = 3;
+ private static final int MSG_CONFIGURE_BRIGHTNESS = 4;
+ private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 5;
+ private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 6;
+ private static final int MSG_STOP = 7;
+ private static final int MSG_UPDATE_BRIGHTNESS = 8;
+ private static final int MSG_UPDATE_RBC = 9;
+ private static final int MSG_BRIGHTNESS_RAMP_DONE = 10;
+ private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
- // Trigger proximity if distance is less than 5 cm.
- private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
// State machine constants for tracking initial brightness ramp skipping when enabled.
private static final int RAMP_STATE_SKIP_NONE = 0;
@@ -200,9 +186,6 @@
// Tracker for brightness settings changes.
private final SettingsObserver mSettingsObserver;
- // The proximity sensor, or null if not available or needed.
- private Sensor mProximitySensor;
-
// The doze screen brightness.
private final float mScreenBrightnessDozeConfig;
@@ -227,9 +210,6 @@
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
- // True if should use light sensor to automatically determine doze screen brightness.
- private final boolean mAllowAutoBrightnessWhileDozingConfig;
-
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -266,10 +246,6 @@
@GuardedBy("mLock")
private DisplayPowerRequest mPendingRequestLocked;
- // True if a request has been made to wait for the proximity sensor to go negative.
- @GuardedBy("mLock")
- private boolean mPendingWaitForNegativeProximityLocked;
-
// True if the pending power request or wait for negative proximity flag
// has been changed since the last update occurred.
@GuardedBy("mLock")
@@ -296,37 +272,7 @@
// Must only be accessed on the handler thread.
private DisplayPowerState mPowerState;
- // True if the device should wait for negative proximity sensor before
- // waking up the screen. This is set to false as soon as a negative
- // proximity sensor measurement is observed or when the device is forced to
- // go to sleep by the user. While true, the screen remains off.
- private boolean mWaitingForNegativeProximity;
- // True if the device should not take into account the proximity sensor
- // until either the proximity sensor state changes, or there is no longer a
- // request to listen to proximity sensor.
- private boolean mIgnoreProximityUntilChanged;
-
- // The actual proximity sensor threshold value.
- private float mProximityThreshold;
-
- // Set to true if the proximity sensor listener has been registered
- // with the sensor manager.
- private boolean mProximitySensorEnabled;
-
- // The debounced proximity sensor state.
- private int mProximity = PROXIMITY_UNKNOWN;
-
- // The raw non-debounced proximity sensor state.
- private int mPendingProximity = PROXIMITY_UNKNOWN;
-
- // -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will
- // be removed. Applies for both positive and negative proximity flips.
- private long mPendingProximityDebounceTime = -1;
-
- // True if the screen was turned off because of the proximity sensor.
- // When the screen turns on again, we report user activity to the power manager.
- private boolean mScreenOffBecauseOfProximity;
// The currently active screen on unblocker. This field is non-null whenever
// we are waiting for a callback to release it and unblock the screen.
@@ -400,6 +346,8 @@
private final BrightnessEvent mLastBrightnessEvent;
private final BrightnessEvent mTempBrightnessEvent;
+ private final DisplayBrightnessController mDisplayBrightnessController;
+
// Keeps a record of brightness changes for dumpsys.
private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
@@ -407,6 +355,9 @@
// a medium of communication between this class and the PowerManagerService.
private final WakelockController mWakelockController;
+ // Tracks and manages the proximity state of the associated display.
+ private final DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -491,13 +442,20 @@
mClock = mInjector.getClock();
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+ mSensorManager = sensorManager;
+ mHandler = new DisplayControllerHandler(handler.getLooper());
+ mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
+ .getDisplayDeviceConfig();
mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
+ mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
+ () -> updatePowerState(), mDisplayId, mSensorManager);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
mDisplayStatsId = mUniqueDisplayId.hashCode();
- mHandler = new DisplayControllerHandler(handler.getLooper());
+
mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
@@ -508,7 +466,6 @@
}
mSettingsObserver = new SettingsObserver(mHandler);
- mSensorManager = sensorManager;
mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
mBlanker = blanker;
mContext = context;
@@ -542,12 +499,6 @@
mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
- mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
- R.bool.config_allowAutoBrightnessWhileDozing);
-
- mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceConfig();
-
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
@@ -611,8 +562,8 @@
mBrightnessBucketsInDozeConfig = resources.getBoolean(
R.bool.config_displayBrightnessBucketsInDoze);
- loadProximitySensor();
-
+ mDisplayBrightnessController =
+ new DisplayBrightnessController(context, null, mDisplayId);
mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
@@ -653,7 +604,7 @@
*/
@Override
public boolean isProximitySensorAvailable() {
- return mProximitySensor != null;
+ return mDisplayPowerProximityStateController.isProximitySensorAvailable();
}
/**
@@ -727,13 +678,8 @@
return true;
}
- boolean changed = false;
-
- if (waitForNegativeProximity
- && !mPendingWaitForNegativeProximityLocked) {
- mPendingWaitForNegativeProximityLocked = true;
- changed = true;
- }
+ boolean changed = mDisplayPowerProximityStateController
+ .setPendingWaitForNegativeProximityLocked(waitForNegativeProximity);
if (mPendingRequestLocked == null) {
mPendingRequestLocked = new DisplayPowerRequest(request);
@@ -790,6 +736,7 @@
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
loadFromDisplayDeviceConfig(token, info);
+ mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
updatePowerState();
}
});
@@ -838,7 +785,6 @@
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
- loadProximitySensor();
loadNitsRange(mContext.getResources());
setUpAutoBrightness(mContext.getResources(), mHandler);
reloadReduceBrightColours();
@@ -903,7 +849,7 @@
// Initialize all of the brightness tracking state
final float brightness = convertToNits(mPowerState.getScreenBrightness());
- if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+ if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
mBrightnessSettingListener = brightnessValue -> {
@@ -1035,7 +981,9 @@
}
loadAmbientLightSensor();
- if (mBrightnessTracker != null) {
+ // BrightnessTracker should only use one light sensor, we want to use the light sensor
+ // from the default display and not e.g. temporary displays when switching layouts.
+ if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
mBrightnessTracker.setLightSensor(mLightSensor);
}
@@ -1132,7 +1080,7 @@
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
- setProximitySensorEnabled(false);
+ mDisplayPowerProximityStateController.cleanup();
mHbmController.stop();
mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
@@ -1164,7 +1112,6 @@
final int previousPolicy;
boolean mustInitialize = false;
int brightnessAdjustmentFlags = 0;
- mBrightnessReasonTemp.set(null);
mTempBrightnessEvent.reset();
synchronized (mLock) {
if (mStopped) {
@@ -1177,7 +1124,7 @@
if (mPowerRequest == null) {
mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
- updatePendingProximityRequestsLocked();
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
mPendingRequestChangedLocked = false;
mustInitialize = true;
// Assume we're on and bright until told otherwise, since that's the state we turn
@@ -1186,7 +1133,7 @@
} else if (mPendingRequestChangedLocked) {
previousPolicy = mPowerRequest.policy;
mPowerRequest.copyFrom(mPendingRequestLocked);
- updatePendingProximityRequestsLocked();
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
mPendingRequestChangedLocked = false;
mDisplayReadyLocked = false;
} else {
@@ -1200,7 +1147,6 @@
// We might override this below based on other factors.
// Initialise brightness as invalid.
int state;
- float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
boolean performScreenOffTransition = false;
switch (mPowerRequest.policy) {
case DisplayPowerRequest.POLICY_OFF:
@@ -1213,10 +1159,6 @@
} else {
state = Display.STATE_DOZE;
}
- if (!mAllowAutoBrightnessWhileDozingConfig) {
- brightnessState = mPowerRequest.dozeScreenBrightness;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
- }
break;
case DisplayPowerRequest.POLICY_VR:
state = Display.STATE_VR;
@@ -1229,55 +1171,11 @@
}
assert (state != Display.STATE_UNKNOWN);
- boolean skipRampBecauseOfProximityChangeToNegative = false;
- // Apply the proximity sensor.
- if (mProximitySensor != null) {
- if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
- // At this point the policy says that the screen should be on, but we've been
- // asked to listen to the prox sensor to adjust the display state, so lets make
- // sure the sensor is on.
- setProximitySensorEnabled(true);
- if (!mScreenOffBecauseOfProximity
- && mProximity == PROXIMITY_POSITIVE
- && !mIgnoreProximityUntilChanged) {
- // Prox sensor already reporting "near" so we should turn off the screen.
- // Also checked that we aren't currently set to ignore the proximity sensor
- // temporarily.
- mScreenOffBecauseOfProximity = true;
- sendOnProximityPositiveWithWakelock();
- }
- } else if (mWaitingForNegativeProximity
- && mScreenOffBecauseOfProximity
- && mProximity == PROXIMITY_POSITIVE
- && state != Display.STATE_OFF) {
- // The policy says that we should have the screen on, but it's off due to the prox
- // and we've been asked to wait until the screen is far from the user to turn it
- // back on. Let keep the prox sensor on so we can tell when it's far again.
- setProximitySensorEnabled(true);
- } else {
- // We haven't been asked to use the prox sensor and we're not waiting on the screen
- // to turn back on...so lets shut down the prox sensor.
- setProximitySensorEnabled(false);
- mWaitingForNegativeProximity = false;
- }
-
- if (mScreenOffBecauseOfProximity
- && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
- // The screen *was* off due to prox being near, but now it's "far" so lets turn
- // the screen back on. Also turn it back on if we've been asked to ignore the
- // prox sensor temporarily.
- mScreenOffBecauseOfProximity = false;
- skipRampBecauseOfProximityChangeToNegative = true;
- sendOnProximityNegativeWithWakelock();
- }
- } else {
- mWaitingForNegativeProximity = false;
- mIgnoreProximityUntilChanged = false;
- }
+ mDisplayPowerProximityStateController.updateProximityState(mPowerRequest, state);
if (!mLogicalDisplay.isEnabled()
|| mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
- || mScreenOffBecauseOfProximity) {
+ || mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
state = Display.STATE_OFF;
}
@@ -1293,10 +1191,10 @@
animateScreenStateChange(state, performScreenOffTransition);
state = mPowerState.getScreenState();
- if (state == Display.STATE_OFF) {
- brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
- }
+ DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
+ .updateBrightness(mPowerRequest, state);
+ float brightnessState = displayBrightnessState.getBrightness();
+ mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
// Always use the VR brightness when in the VR state.
if (state == Display.STATE_VR) {
@@ -1314,7 +1212,8 @@
}
final boolean autoBrightnessEnabledInDoze =
- mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
+ mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
+ && Display.isDozeState(state);
final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& Float.isNaN(brightnessState)
@@ -1548,7 +1447,8 @@
final boolean wasOrWillBeInVr =
(state == Display.STATE_VR || oldState == Display.STATE_VR);
final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
- != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
+ != RAMP_STATE_SKIP_NONE) || mDisplayPowerProximityStateController
+ .shouldSkipRampBecauseOfProximityChangeToNegative();
// While dozing, sometimes the brightness is split into buckets. Rather than animating
// through the buckets, which is unlikely to be smooth in the first place, just jump
// right to the suggested brightness.
@@ -1662,6 +1562,7 @@
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+ mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1672,12 +1573,6 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setFastAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
- mTempBrightnessEvent.setSlowAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -1768,7 +1663,7 @@
*/
@Override
public void ignoreProximitySensorUntilChanged() {
- mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
}
@Override
@@ -1934,7 +1829,7 @@
|| mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
// If we are trying to turn screen off, give policy a chance to do something before we
// actually turn the screen off.
- if (isOff && !mScreenOffBecauseOfProximity) {
+ if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
|| mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
@@ -1964,7 +1859,7 @@
// it is only removed once the window manager tells us that the activity has
// finished drawing underneath.
if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
- && !mScreenOffBecauseOfProximity) {
+ && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
unblockScreenOn();
mWindowManagerPolicy.screenTurnedOff(mDisplayId);
@@ -2006,22 +1901,6 @@
fallbackType);
}
- private void loadProximitySensor() {
- if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
- return;
- }
- final DisplayDeviceConfig.SensorData proxSensor =
- mDisplayDeviceConfig.getProximitySensor();
- final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
- mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
- fallbackType);
- if (mProximitySensor != null) {
- mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
- TYPICAL_PROXIMITY_THRESHOLD);
- }
- }
-
private float clampScreenBrightnessForVr(float value) {
return MathUtils.constrain(
value, mScreenBrightnessForVrRangeMinimum,
@@ -2223,98 +2102,6 @@
private final Runnable mCleanListener = this::sendUpdatePowerState;
- private void setProximitySensorEnabled(boolean enable) {
- if (enable) {
- if (!mProximitySensorEnabled) {
- // Register the listener.
- // Proximity sensor state already cleared initially.
- mProximitySensorEnabled = true;
- mIgnoreProximityUntilChanged = false;
- mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
- SensorManager.SENSOR_DELAY_NORMAL, mHandler);
- }
- } else {
- if (mProximitySensorEnabled) {
- // Unregister the listener.
- // Clear the proximity sensor state for next time.
- mProximitySensorEnabled = false;
- mProximity = PROXIMITY_UNKNOWN;
- mIgnoreProximityUntilChanged = false;
- mPendingProximity = PROXIMITY_UNKNOWN;
- mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
- mSensorManager.unregisterListener(mProximitySensorListener);
- // release wake lock(must be last)
- boolean proxDebounceSuspendBlockerReleased =
- mWakelockController.releaseWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
- if (proxDebounceSuspendBlockerReleased) {
- mPendingProximityDebounceTime = -1;
- }
- }
- }
- }
-
- private void handleProximitySensorEvent(long time, boolean positive) {
- if (mProximitySensorEnabled) {
- if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
- return; // no change
- }
- if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
- return; // no change
- }
-
- // Only accept a proximity sensor reading if it remains
- // stable for the entire debounce delay. We hold a wake lock while
- // debouncing the sensor.
- mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
- if (positive) {
- mPendingProximity = PROXIMITY_POSITIVE;
- mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
- mWakelockController.acquireWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
- } else {
- mPendingProximity = PROXIMITY_NEGATIVE;
- mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
- mWakelockController.acquireWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
- }
-
- // Debounce the new sensor reading.
- debounceProximitySensor();
- }
- }
-
- private void debounceProximitySensor() {
- if (mProximitySensorEnabled
- && mPendingProximity != PROXIMITY_UNKNOWN
- && mPendingProximityDebounceTime >= 0) {
- final long now = mClock.uptimeMillis();
- if (mPendingProximityDebounceTime <= now) {
- if (mProximity != mPendingProximity) {
- // if the status of the sensor changed, stop ignoring.
- mIgnoreProximityUntilChanged = false;
- Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
- }
- // Sensor reading accepted. Apply the change then release the wake lock.
- mProximity = mPendingProximity;
- updatePowerState();
- // (must be last)
- boolean proxDebounceSuspendBlockerReleased =
- mWakelockController.releaseWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
- if (proxDebounceSuspendBlockerReleased) {
- mPendingProximityDebounceTime = -1;
- }
-
- } else {
- // Need to wait a little longer.
- // Debounce again later. We continue holding a wake lock while waiting.
- Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
- mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
- }
- }
- }
-
private void sendOnStateChangedWithWakelock() {
boolean wakeLockAcquired = mWakelockController.acquireWakelock(
WakelockController.WAKE_LOCK_STATE_CHANGED);
@@ -2439,7 +2226,7 @@
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
- && mAutomaticBrightnessController != null) {
+ && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
// values into a physical brightness unit since the value provided by the API is in
// nits and not using the arbitrary backlight units.
@@ -2459,39 +2246,6 @@
return mAutomaticBrightnessController.convertToNits(brightness);
}
- @GuardedBy("mLock")
- private void updatePendingProximityRequestsLocked() {
- mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
- mPendingWaitForNegativeProximityLocked = false;
-
- if (mIgnoreProximityUntilChanged) {
- // Also, lets stop waiting for negative proximity if we're ignoring it.
- mWaitingForNegativeProximity = false;
- }
- }
-
- private void ignoreProximitySensorUntilChangedInternal() {
- if (!mIgnoreProximityUntilChanged
- && mProximity == PROXIMITY_POSITIVE) {
- // Only ignore if it is still reporting positive (near)
- mIgnoreProximityUntilChanged = true;
- Slog.i(mTag, "Ignoring proximity");
- updatePowerState();
- }
- }
-
- private void sendOnProximityPositiveWithWakelock() {
- mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
- mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
- }
-
-
- private void sendOnProximityNegativeWithWakelock() {
- mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
- mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
- }
-
-
@Override
public void dump(final PrintWriter pw) {
synchronized (mLock) {
@@ -2505,8 +2259,6 @@
pw.println(" mDisplayReadyLocked=" + mDisplayReadyLocked);
pw.println(" mPendingRequestLocked=" + mPendingRequestLocked);
pw.println(" mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
- pw.println(" mPendingWaitForNegativeProximityLocked="
- + mPendingWaitForNegativeProximityLocked);
pw.println(" mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
}
@@ -2519,8 +2271,6 @@
pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
- pw.println(" mAllowAutoBrightnessWhileDozingConfig="
- + mAllowAutoBrightnessWhileDozingConfig);
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
@@ -2541,7 +2291,6 @@
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
-
mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
}
@@ -2549,15 +2298,6 @@
pw.println();
pw.println("Display Power Controller Thread State:");
pw.println(" mPowerRequest=" + mPowerRequest);
- pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
- pw.println(" mProximitySensor=" + mProximitySensor);
- pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled);
- pw.println(" mProximityThreshold=" + mProximityThreshold);
- pw.println(" mProximity=" + proximityToString(mProximity));
- pw.println(" mPendingProximity=" + proximityToString(mPendingProximity));
- pw.println(" mPendingProximityDebounceTime="
- + TimeUtils.formatUptime(mPendingProximityDebounceTime));
- pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
pw.println(" mPendingScreenBrightnessSetting="
+ mPendingScreenBrightnessSetting);
@@ -2629,21 +2369,18 @@
if (mWakelockController != null) {
mWakelockController.dumpLocal(pw);
}
- }
- private static String proximityToString(int state) {
- switch (state) {
- case PROXIMITY_UNKNOWN:
- return "Unknown";
- case PROXIMITY_NEGATIVE:
- return "Negative";
- case PROXIMITY_POSITIVE:
- return "Positive";
- default:
- return Integer.toString(state);
+ pw.println();
+ if (mDisplayBrightnessController != null) {
+ mDisplayBrightnessController.dump(pw);
+ }
+
+ if (mDisplayPowerProximityStateController != null) {
+ mDisplayPowerProximityStateController.dumpLocal(pw);
}
}
+
private static String reportedToPolicyToString(int state) {
switch (state) {
case REPORTED_TO_POLICY_SCREEN_OFF:
@@ -2766,19 +2503,22 @@
float appliedThermalCapNits =
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
? -1f : convertToNits(event.getThermalMax());
-
- FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- convertToNits(event.getInitialBrightness()),
- convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
- event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
- appliedLowPowerMode,
- appliedRbcStrength,
- appliedHbmMaxNits,
- appliedThermalCapNits,
- event.isAutomaticBrightnessEnabled(),
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+ && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+ .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+ convertToNits(event.getInitialBrightness()),
+ convertToNits(event.getBrightness()),
+ event.getLux(),
+ event.getPhysicalDisplayId(),
+ event.wasShortTermModelActive(),
+ appliedLowPowerMode,
+ appliedRbcStrength,
+ appliedHbmMaxNits,
+ appliedThermalCapNits,
+ event.isAutomaticBrightnessEnabled(),
+ FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ }
}
private final class DisplayControllerHandler extends Handler {
@@ -2793,10 +2533,6 @@
updatePowerState();
break;
- case MSG_PROXIMITY_SENSOR_DEBOUNCED:
- debounceProximitySensor();
- break;
-
case MSG_SCREEN_ON_UNBLOCKED:
if (mPendingScreenOnUnblocker == msg.obj) {
unblockScreenOn();
@@ -2825,10 +2561,6 @@
updatePowerState();
break;
- case MSG_IGNORE_PROXIMITY:
- ignoreProximitySensorUntilChangedInternal();
- break;
-
case MSG_STOP:
cleanupHandlerThreadAfterStop();
break;
@@ -2858,23 +2590,6 @@
}
}
- private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (mProximitySensorEnabled) {
- final long time = mClock.uptimeMillis();
- final float distance = event.values[0];
- boolean positive = distance >= 0.0f && distance < mProximityThreshold;
- handleProximitySensorEvent(time, positive);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // Not used.
- }
- };
-
private final class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
@@ -2964,6 +2679,15 @@
DisplayPowerCallbacks displayPowerCallbacks) {
return new WakelockController(displayId, displayPowerCallbacks);
}
+
+ DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+ WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+ Looper looper, Runnable nudgeUpdatePowerState,
+ int displayId, SensorManager sensorManager) {
+ return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
+ looper, nudgeUpdatePowerState,
+ displayId, sensorManager, /* injector= */ null);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
new file mode 100644
index 0000000..a3433d9
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -0,0 +1,550 @@
+/*
+ * 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.server.display;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.SensorUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Maintains the proximity state of the display.
+ * Internally listens for proximity updates and schedules a power state update when the proximity
+ * state changes.
+ */
+public final class DisplayPowerProximityStateController {
+ @VisibleForTesting
+ static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+ @VisibleForTesting
+ static final int PROXIMITY_UNKNOWN = -1;
+ @VisibleForTesting
+ static final int PROXIMITY_POSITIVE = 1;
+ @VisibleForTesting
+ static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
+ private static final int MSG_IGNORE_PROXIMITY = 2;
+
+ private static final int PROXIMITY_NEGATIVE = 0;
+
+ private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+ // Proximity sensor debounce delay in milliseconds for positive transitions.
+
+ // Proximity sensor debounce delay in milliseconds for negative transitions.
+ private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+ // Trigger proximity if distance is less than 5 cm.
+ private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
+
+ private final String mTag;
+ // A lock to handle the deadlock and race conditions.
+ private final Object mLock = new Object();
+ // The manager which lets us access the device's ProximitySensor
+ private final SensorManager mSensorManager;
+ // An entity which manages the wakelocks.
+ private final WakelockController mWakelockController;
+ // A handler to process all the events on this thread in a synchronous manner
+ private final DisplayPowerProximityStateHandler mHandler;
+ // A runnable to execute the utility to update the power state.
+ private final Runnable mNudgeUpdatePowerState;
+ private Clock mClock;
+ // A listener which listen's to the events emitted by the proximity sensor.
+ private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mProximitySensorEnabled) {
+ final long time = mClock.uptimeMillis();
+ final float distance = event.values[0];
+ boolean positive = distance >= 0.0f && distance < mProximityThreshold;
+ handleProximitySensorEvent(time, positive);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Not used.
+ }
+ };
+
+ // The proximity sensor, or null if not available or needed.
+ private Sensor mProximitySensor;
+
+ // The configurations for the associated display
+ private DisplayDeviceConfig mDisplayDeviceConfig;
+
+ // True if a request has been made to wait for the proximity sensor to go negative.
+ @GuardedBy("mLock")
+ private boolean mPendingWaitForNegativeProximityLocked;
+
+ // True if the device should wait for negative proximity sensor before
+ // waking up the screen. This is set to false as soon as a negative
+ // proximity sensor measurement is observed or when the device is forced to
+ // go to sleep by the user. While true, the screen remains off.
+ private boolean mWaitingForNegativeProximity;
+
+ // True if the device should not take into account the proximity sensor
+ // until either the proximity sensor state changes, or there is no longer a
+ // request to listen to proximity sensor.
+ private boolean mIgnoreProximityUntilChanged;
+
+ // Set to true if the proximity sensor listener has been registered
+ // with the sensor manager.
+ private boolean mProximitySensorEnabled;
+
+ // The raw non-debounced proximity sensor state.
+ private int mPendingProximity = PROXIMITY_UNKNOWN;
+
+ // -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will
+ // be removed. Applies for both positive and negative proximity flips.
+ private long mPendingProximityDebounceTime = -1;
+
+ // True if the screen was turned off because of the proximity sensor.
+ // When the screen turns on again, we report user activity to the power manager.
+ private boolean mScreenOffBecauseOfProximity;
+
+ // The debounced proximity sensor state.
+ private int mProximity = PROXIMITY_UNKNOWN;
+
+ // The actual proximity sensor threshold value.
+ private float mProximityThreshold;
+
+ // A flag representing if the ramp is to be skipped when the proximity changes from positive
+ // to negative
+ private boolean mSkipRampBecauseOfProximityChangeToNegative = false;
+
+ // The DisplayId of the associated Logical Display.
+ private int mDisplayId;
+
+ /**
+ * Create a new instance of DisplayPowerProximityStateController.
+ *
+ * @param wakeLockController WakelockController used to acquire/release wakelocks
+ * @param displayDeviceConfig DisplayDeviceConfig instance from which the configs(Proximity
+ * Sensor) are to be loaded
+ * @param looper A looper onto which the handler is to be associated.
+ * @param nudgeUpdatePowerState A runnable to execute the utility to update the power state
+ * @param displayId The DisplayId of the associated Logical Display.
+ * @param sensorManager The manager which lets us access the display's ProximitySensor
+ */
+ public DisplayPowerProximityStateController(
+ WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig,
+ Looper looper,
+ Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager,
+ Injector injector) {
+ if (injector == null) {
+ injector = new Injector();
+ }
+ mClock = injector.createClock();
+ mWakelockController = wakeLockController;
+ mHandler = new DisplayPowerProximityStateHandler(looper);
+ mNudgeUpdatePowerState = nudgeUpdatePowerState;
+ mDisplayDeviceConfig = displayDeviceConfig;
+ mDisplayId = displayId;
+ mTag = "DisplayPowerProximityStateController[" + mDisplayId + "]";
+ mSensorManager = sensorManager;
+ loadProximitySensor();
+ }
+
+ /**
+ * Manages the pending state of the proximity.
+ */
+ public void updatePendingProximityRequestsLocked() {
+ synchronized (mLock) {
+ mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+ mPendingWaitForNegativeProximityLocked = false;
+
+ if (mIgnoreProximityUntilChanged) {
+ // Also, lets stop waiting for negative proximity if we're ignoring it.
+ mWaitingForNegativeProximity = false;
+ }
+ }
+ }
+
+ /**
+ * Clean up all resources that are accessed via the {@link #mHandler} thread.
+ */
+ public void cleanup() {
+ setProximitySensorEnabled(false);
+ }
+
+ /**
+ * Returns true if the proximity sensor screen-off function is available.
+ */
+ public boolean isProximitySensorAvailable() {
+ return mProximitySensor != null;
+ }
+
+ /**
+ * Sets the flag to indicate that the system is waiting for the negative proximity event
+ */
+ public boolean setPendingWaitForNegativeProximityLocked(
+ boolean requestWaitForNegativeProximity) {
+ synchronized (mLock) {
+ if (requestWaitForNegativeProximity
+ && !mPendingWaitForNegativeProximityLocked) {
+ mPendingWaitForNegativeProximityLocked = true;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Updates the proximity state of the display, based on the newly received DisplayPowerRequest
+ * and the target display state
+ */
+ public void updateProximityState(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+ int displayState) {
+ mSkipRampBecauseOfProximityChangeToNegative = false;
+ if (mProximitySensor != null) {
+ if (displayPowerRequest.useProximitySensor && displayState != Display.STATE_OFF) {
+ // At this point the policy says that the screen should be on, but we've been
+ // asked to listen to the prox sensor to adjust the display state, so lets make
+ // sure the sensor is on.
+ setProximitySensorEnabled(true);
+ if (!mScreenOffBecauseOfProximity
+ && mProximity == PROXIMITY_POSITIVE
+ && !mIgnoreProximityUntilChanged) {
+ // Prox sensor already reporting "near" so we should turn off the screen.
+ // Also checked that we aren't currently set to ignore the proximity sensor
+ // temporarily.
+ mScreenOffBecauseOfProximity = true;
+ sendOnProximityPositiveWithWakelock();
+ }
+ } else if (mWaitingForNegativeProximity
+ && mScreenOffBecauseOfProximity
+ && mProximity == PROXIMITY_POSITIVE
+ && displayState != Display.STATE_OFF) {
+ // The policy says that we should have the screen on, but it's off due to the prox
+ // and we've been asked to wait until the screen is far from the user to turn it
+ // back on. Let keep the prox sensor on so we can tell when it's far again.
+ setProximitySensorEnabled(true);
+ } else {
+ // We haven't been asked to use the prox sensor and we're not waiting on the screen
+ // to turn back on...so let's shut down the prox sensor.
+ setProximitySensorEnabled(false);
+ mWaitingForNegativeProximity = false;
+ }
+ if (mScreenOffBecauseOfProximity
+ && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
+ // The screen *was* off due to prox being near, but now it's "far" so lets turn
+ // the screen back on. Also turn it back on if we've been asked to ignore the
+ // prox sensor temporarily.
+ mScreenOffBecauseOfProximity = false;
+ mSkipRampBecauseOfProximityChangeToNegative = true;
+ sendOnProximityNegativeWithWakelock();
+ }
+ } else {
+ mWaitingForNegativeProximity = false;
+ mIgnoreProximityUntilChanged = false;
+ }
+ }
+
+ /**
+ * A utility to check if the brightness change ramp is to be skipped because the proximity was
+ * changed from positive to negative.
+ */
+ public boolean shouldSkipRampBecauseOfProximityChangeToNegative() {
+ return mSkipRampBecauseOfProximityChangeToNegative;
+ }
+
+ /**
+ * Represents of the screen is currently turned off because of the proximity state.
+ */
+ public boolean isScreenOffBecauseOfProximity() {
+ return mScreenOffBecauseOfProximity;
+ }
+
+ /**
+ * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
+ * currently enabled and forcing the screen to be dark.
+ */
+ public void ignoreProximitySensorUntilChanged() {
+ mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+ }
+
+ /**
+ * This adjusts the state of this class when a change in the DisplayDevice is detected.
+ */
+ public void notifyDisplayDeviceChanged(DisplayDeviceConfig displayDeviceConfig) {
+ this.mDisplayDeviceConfig = displayDeviceConfig;
+ loadProximitySensor();
+ }
+
+ /**
+ * Used to dump the state.
+ *
+ * @param pw The PrintWriter used to dump the state.
+ */
+ public void dumpLocal(PrintWriter pw) {
+ pw.println();
+ pw.println("DisplayPowerProximityStateController:");
+ synchronized (mLock) {
+ pw.println(" mPendingWaitForNegativeProximityLocked="
+ + mPendingWaitForNegativeProximityLocked);
+ }
+ pw.println(" mDisplayId=" + mDisplayId);
+ pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
+ pw.println(" mIgnoreProximityUntilChanged=" + mIgnoreProximityUntilChanged);
+ pw.println(" mProximitySensor=" + mProximitySensor);
+ pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled);
+ pw.println(" mProximityThreshold=" + mProximityThreshold);
+ pw.println(" mProximity=" + proximityToString(mProximity));
+ pw.println(" mPendingProximity=" + proximityToString(mPendingProximity));
+ pw.println(" mPendingProximityDebounceTime="
+ + TimeUtils.formatUptime(mPendingProximityDebounceTime));
+ pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
+ pw.println(" mSkipRampBecauseOfProximityChangeToNegative="
+ + mSkipRampBecauseOfProximityChangeToNegative);
+ }
+
+ void ignoreProximitySensorUntilChangedInternal() {
+ if (!mIgnoreProximityUntilChanged
+ && mProximity == PROXIMITY_POSITIVE) {
+ // Only ignore if it is still reporting positive (near)
+ mIgnoreProximityUntilChanged = true;
+ Slog.i(mTag, "Ignoring proximity");
+ mNudgeUpdatePowerState.run();
+ }
+ }
+
+ private void sendOnProximityPositiveWithWakelock() {
+ mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+ mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
+ }
+
+ private void sendOnProximityNegativeWithWakelock() {
+ mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+ mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
+ }
+
+ private void loadProximitySensor() {
+ if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ return;
+ }
+ final DisplayDeviceConfig.SensorData proxSensor =
+ mDisplayDeviceConfig.getProximitySensor();
+ final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
+ ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
+ mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
+ fallbackType);
+ if (mProximitySensor != null) {
+ mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
+ TYPICAL_PROXIMITY_THRESHOLD);
+ }
+ }
+
+ private void setProximitySensorEnabled(boolean enable) {
+ if (enable) {
+ if (!mProximitySensorEnabled) {
+ // Register the listener.
+ // Proximity sensor state already cleared initially.
+ mProximitySensorEnabled = true;
+ mIgnoreProximityUntilChanged = false;
+ mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
+ SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+ }
+ } else {
+ if (mProximitySensorEnabled) {
+ // Unregister the listener.
+ // Clear the proximity sensor state for next time.
+ mProximitySensorEnabled = false;
+ mProximity = PROXIMITY_UNKNOWN;
+ mIgnoreProximityUntilChanged = false;
+ mPendingProximity = PROXIMITY_UNKNOWN;
+ mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ mSensorManager.unregisterListener(mProximitySensorListener);
+ // release wake lock(must be last)
+ boolean proxDebounceSuspendBlockerReleased =
+ mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ if (proxDebounceSuspendBlockerReleased) {
+ mPendingProximityDebounceTime = -1;
+ }
+ }
+ }
+ }
+
+ private void handleProximitySensorEvent(long time, boolean positive) {
+ if (mProximitySensorEnabled) {
+ if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
+ return; // no change
+ }
+ if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
+ return; // no change
+ }
+
+ // Only accept a proximity sensor reading if it remains
+ // stable for the entire debounce delay. We hold a wake lock while
+ // debouncing the sensor.
+ mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ if (positive) {
+ mPendingProximity = PROXIMITY_POSITIVE;
+ mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
+ mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
+ } else {
+ mPendingProximity = PROXIMITY_NEGATIVE;
+ mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
+ mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
+ }
+
+ // Debounce the new sensor reading.
+ debounceProximitySensor();
+ }
+ }
+
+ private void debounceProximitySensor() {
+ if (mProximitySensorEnabled
+ && mPendingProximity != PROXIMITY_UNKNOWN
+ && mPendingProximityDebounceTime >= 0) {
+ final long now = mClock.uptimeMillis();
+ if (mPendingProximityDebounceTime <= now) {
+ if (mProximity != mPendingProximity) {
+ // if the status of the sensor changed, stop ignoring.
+ mIgnoreProximityUntilChanged = false;
+ Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
+ }
+ // Sensor reading accepted. Apply the change then release the wake lock.
+ mProximity = mPendingProximity;
+ mNudgeUpdatePowerState.run();
+ // (must be last)
+ boolean proxDebounceSuspendBlockerReleased =
+ mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ if (proxDebounceSuspendBlockerReleased) {
+ mPendingProximityDebounceTime = -1;
+ }
+
+ } else {
+ // Need to wait a little longer.
+ // Debounce again later. We continue holding a wake lock while waiting.
+ Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+ mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
+ }
+ }
+ }
+
+ private class DisplayPowerProximityStateHandler extends Handler {
+ DisplayPowerProximityStateHandler(Looper looper) {
+ super(looper, null, true /*async*/);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PROXIMITY_SENSOR_DEBOUNCED:
+ debounceProximitySensor();
+ break;
+
+ case MSG_IGNORE_PROXIMITY:
+ ignoreProximitySensorUntilChangedInternal();
+ break;
+ }
+ }
+ }
+
+ private String proximityToString(int state) {
+ switch (state) {
+ case PROXIMITY_UNKNOWN:
+ return "Unknown";
+ case PROXIMITY_NEGATIVE:
+ return "Negative";
+ case PROXIMITY_POSITIVE:
+ return "Positive";
+ default:
+ return Integer.toString(state);
+ }
+ }
+
+ @VisibleForTesting
+ boolean getPendingWaitForNegativeProximityLocked() {
+ synchronized (mLock) {
+ return mPendingWaitForNegativeProximityLocked;
+ }
+ }
+
+ @VisibleForTesting
+ boolean getWaitingForNegativeProximity() {
+ return mWaitingForNegativeProximity;
+ }
+
+ @VisibleForTesting
+ boolean shouldIgnoreProximityUntilChanged() {
+ return mIgnoreProximityUntilChanged;
+ }
+
+ boolean isProximitySensorEnabled() {
+ return mProximitySensorEnabled;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @VisibleForTesting
+ int getPendingProximity() {
+ return mPendingProximity;
+ }
+
+ @VisibleForTesting
+ int getProximity() {
+ return mProximity;
+ }
+
+
+ @VisibleForTesting
+ long getPendingProximityDebounceTime() {
+ return mPendingProximityDebounceTime;
+ }
+
+ @VisibleForTesting
+ SensorEventListener getProximitySensorListener() {
+ return mProximitySensorListener;
+ }
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ Clock createClock() {
+ return () -> SystemClock.uptimeMillis();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2c2075d..5a714f5 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -209,7 +209,7 @@
private int mUserPreferredModeId = INVALID_MODE_ID;
// This is used only for the purpose of testing, to verify if the mode was correct when the
// device started or booted.
- private int mActiveDisplayModeAtStartId = INVALID_MODE_ID;
+ private int mActiveSfDisplayModeAtStartId = INVALID_MODE_ID;
private Display.Mode mUserPreferredMode;
private int mActiveModeId = INVALID_MODE_ID;
private boolean mDisplayModeSpecsInvalid;
@@ -241,7 +241,7 @@
mSidekickInternal = LocalServices.getService(SidekickInternal.class);
mBacklightAdapter = new BacklightAdapter(displayToken, isFirstDisplay,
mSurfaceControlProxy);
- mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
+ mActiveSfDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
}
@Override
@@ -255,7 +255,7 @@
*/
@Override
public Display.Mode getActiveDisplayModeAtStartLocked() {
- return findMode(mActiveDisplayModeAtStartId);
+ return findMode(findMatchingModeIdLocked(mActiveSfDisplayModeAtStartId));
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index e3fa622..f19852b 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -39,8 +39,6 @@
private String mPhysicalDisplayId;
private long mTime;
private float mLux;
- private float mFastAmbientLux;
- private float mSlowAmbientLux;
private float mPreThresholdLux;
private float mInitialBrightness;
private float mBrightness;
@@ -51,6 +49,7 @@
private int mRbcStrength;
private float mThermalMax;
private float mPowerFactor;
+ private boolean mWasShortTermModelActive;
private int mFlags;
private int mAdjustmentFlags;
private boolean mAutomaticBrightnessEnabled;
@@ -76,8 +75,6 @@
mTime = that.getTime();
// Lux values
mLux = that.getLux();
- mFastAmbientLux = that.getFastAmbientLux();
- mSlowAmbientLux = that.getSlowAmbientLux();
mPreThresholdLux = that.getPreThresholdLux();
// Brightness values
mInitialBrightness = that.getInitialBrightness();
@@ -90,6 +87,7 @@
mRbcStrength = that.getRbcStrength();
mThermalMax = that.getThermalMax();
mPowerFactor = that.getPowerFactor();
+ mWasShortTermModelActive = that.wasShortTermModelActive();
mFlags = that.getFlags();
mAdjustmentFlags = that.getAdjustmentFlags();
// Auto-brightness setting
@@ -105,8 +103,6 @@
mPhysicalDisplayId = "";
// Lux values
mLux = 0;
- mFastAmbientLux = 0;
- mSlowAmbientLux = 0;
mPreThresholdLux = 0;
// Brightness values
mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -119,6 +115,7 @@
mRbcStrength = 0;
mThermalMax = PowerManager.BRIGHTNESS_MAX;
mPowerFactor = 1f;
+ mWasShortTermModelActive = false;
mFlags = 0;
mAdjustmentFlags = 0;
// Auto-brightness setting
@@ -140,10 +137,6 @@
&& mDisplayId == that.mDisplayId
&& mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
&& Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
- && Float.floatToRawIntBits(mFastAmbientLux)
- == Float.floatToRawIntBits(that.mFastAmbientLux)
- && Float.floatToRawIntBits(mSlowAmbientLux)
- == Float.floatToRawIntBits(that.mSlowAmbientLux)
&& Float.floatToRawIntBits(mPreThresholdLux)
== Float.floatToRawIntBits(that.mPreThresholdLux)
&& Float.floatToRawIntBits(mInitialBrightness)
@@ -161,6 +154,7 @@
== Float.floatToRawIntBits(that.mThermalMax)
&& Float.floatToRawIntBits(mPowerFactor)
== Float.floatToRawIntBits(that.mPowerFactor)
+ && mWasShortTermModelActive == that.mWasShortTermModelActive
&& mFlags == that.mFlags
&& mAdjustmentFlags == that.mAdjustmentFlags
&& mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
@@ -182,14 +176,13 @@
+ ", rcmdBrt=" + mRecommendedBrightness
+ ", preBrt=" + mPreThresholdBrightness
+ ", lux=" + mLux
- + ", fastLux=" + mFastAmbientLux
- + ", slowLux=" + mSlowAmbientLux
+ ", preLux=" + mPreThresholdLux
+ ", hbmMax=" + mHbmMax
+ ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+ ", rbcStrength=" + mRbcStrength
+ ", thrmMax=" + mThermalMax
+ ", powerFactor=" + mPowerFactor
+ + ", wasShortTermModelActive=" + mWasShortTermModelActive
+ ", flags=" + flagsToString()
+ ", reason=" + mReason.toString(mAdjustmentFlags)
+ ", autoBrightness=" + mAutomaticBrightnessEnabled;
@@ -240,22 +233,6 @@
this.mLux = lux;
}
- public float getFastAmbientLux() {
- return mFastAmbientLux;
- }
-
- public void setFastAmbientLux(float mFastAmbientLux) {
- this.mFastAmbientLux = mFastAmbientLux;
- }
-
- public float getSlowAmbientLux() {
- return mSlowAmbientLux;
- }
-
- public void setSlowAmbientLux(float mSlowAmbientLux) {
- this.mSlowAmbientLux = mSlowAmbientLux;
- }
-
public float getPreThresholdLux() {
return mPreThresholdLux;
}
@@ -344,6 +321,20 @@
return (mFlags & FLAG_LOW_POWER_MODE) != 0;
}
+ /**
+ * Set whether the short term model was active before the brightness event.
+ */
+ public boolean setWasShortTermModelActive(boolean wasShortTermModelActive) {
+ return this.mWasShortTermModelActive = wasShortTermModelActive;
+ }
+
+ /**
+ * Returns whether the short term model was active before the brightness event.
+ */
+ public boolean wasShortTermModelActive() {
+ return this.mWasShortTermModelActive;
+ }
+
public int getFlags() {
return mFlags;
}
@@ -352,10 +343,6 @@
this.mFlags = flags;
}
- public boolean isShortTermModelActive() {
- return (mFlags & FLAG_USER_SET) != 0;
- }
-
public int getAdjustmentFlags() {
return mAdjustmentFlags;
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
new file mode 100644
index 0000000..fe4c101
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -0,0 +1,96 @@
+/*
+ * 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.server.display.brightness;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+import java.io.PrintWriter;
+
+/**
+ * Deploys different DozeBrightnessStrategy to choose the current brightness for a specified
+ * display. Applies the chosen brightness.
+ */
+public final class DisplayBrightnessController {
+ private final int mDisplayId;
+ // Selects an appropriate strategy based on the request provided by the clients.
+ private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+
+ /**
+ * The constructor of DisplayBrightnessController.
+ */
+ public DisplayBrightnessController(Context context, Injector injector, int displayId) {
+ if (injector == null) {
+ injector = new Injector();
+ }
+ mDisplayId = displayId;
+ mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
+ displayId);
+ }
+
+ /**
+ * Updates the display brightness. This delegates the responsibility of selecting an appropriate
+ * strategy to DisplayBrightnessStrategySelector, which is then applied to evaluate the
+ * DisplayBrightnessState. In the future,
+ * 1. This will account for clamping the brightness if needed.
+ * 2. This will notify the system about the updated brightness
+ *
+ * @param displayPowerRequest The request to update the brightness
+ * @param targetDisplayState The target display state of the system
+ */
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+ int targetDisplayState) {
+ DisplayBrightnessStrategy displayBrightnessStrategy =
+ mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ targetDisplayState);
+ return displayBrightnessStrategy.updateBrightness(displayPowerRequest);
+ }
+
+ /**
+ * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
+ * brightness when dozing
+ */
+ public boolean isAllowAutoBrightnessWhileDozingConfig() {
+ return mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozingConfig();
+ }
+
+ /**
+ * Used to dump the state.
+ *
+ * @param writer The PrintWriter used to dump the state.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println();
+ writer.println("DisplayBrightnessController:");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+ mDisplayBrightnessStrategySelector.dump(ipw);
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
+ int displayId) {
+ return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
new file mode 100644
index 0000000..88707f0
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -0,0 +1,145 @@
+/*
+ * 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.server.display.brightness;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+
+import java.io.PrintWriter;
+
+/**
+ * This maintains the logic needed to decide the eligible display brightness strategy.
+ */
+public class DisplayBrightnessStrategySelector {
+ private static final String TAG = "DisplayBrightnessStrategySelector";
+ // True if light sensor is to be used to automatically determine doze screen brightness.
+ private final boolean mAllowAutoBrightnessWhileDozingConfig;
+
+ // The brightness strategy used to manage the brightness state when the display is dozing.
+ private final DozeBrightnessStrategy mDozeBrightnessStrategy;
+ // The brightness strategy used to manage the brightness state when the display is in
+ // screen off state.
+ private final ScreenOffBrightnessStrategy mScreenOffBrightnessStrategy;
+ // The brightness strategy used to manage the brightness state when the request state is
+ // invalid.
+ private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+
+ // We take note of the old brightness strategy so that we can know when the strategy changes.
+ private String mOldBrightnessStrategyName;
+
+ private final int mDisplayId;
+
+ /**
+ * The constructor of DozeBrightnessStrategy.
+ */
+ public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId) {
+ if (injector == null) {
+ injector = new Injector();
+ }
+ mDisplayId = displayId;
+ mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
+ mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
+ mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
+ mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
+ R.bool.config_allowAutoBrightnessWhileDozing);
+ mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
+ }
+
+ /**
+ * Selects the appropriate DisplayBrightnessStrategy based on the request and the display state
+ * to which the display is transitioning
+ */
+ @NonNull
+ public DisplayBrightnessStrategy selectStrategy(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+ int targetDisplayState) {
+ DisplayBrightnessStrategy displayBrightnessStrategy = mInvalidBrightnessStrategy;
+ if (targetDisplayState == Display.STATE_OFF) {
+ displayBrightnessStrategy = mScreenOffBrightnessStrategy;
+ } else if (shouldUseDozeBrightnessStrategy(displayPowerRequest)) {
+ displayBrightnessStrategy = mDozeBrightnessStrategy;
+ }
+
+ if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
+ Slog.i(TAG,
+ "Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName
+ + " to" + displayBrightnessStrategy.getName() + " for display "
+ + mDisplayId);
+ mOldBrightnessStrategyName = displayBrightnessStrategy.getName();
+ }
+ return displayBrightnessStrategy;
+ }
+
+ /**
+ * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
+ * brightness when dozing
+ */
+ public boolean isAllowAutoBrightnessWhileDozingConfig() {
+ return mAllowAutoBrightnessWhileDozingConfig;
+ }
+
+ /**
+ * Dumps the state of this class.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println();
+ writer.println("DisplayBrightnessStrategySelector:");
+ writer.println(
+ " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig);
+ }
+
+ /**
+ * Validates if the conditions are met to qualify for the DozeBrightnessStrategy.
+ */
+ private boolean shouldUseDozeBrightnessStrategy(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ // We are not checking the targetDisplayState, but rather relying on the policy because
+ // a user can define a different display state(displayPowerRequest.dozeScreenState) too
+ // in the request with the Doze policy
+ if (displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE) {
+ if (!mAllowAutoBrightnessWhileDozingConfig) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
+ return new ScreenOffBrightnessStrategy();
+ }
+
+ DozeBrightnessStrategy getDozeBrightnessStrategy() {
+ return new DozeBrightnessStrategy();
+ }
+
+ InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
+ return new InvalidBrightnessStrategy();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java
deleted file mode 100644
index 3be5933..0000000
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java
+++ /dev/null
@@ -1,50 +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.server.display.brightness.strategy;
-
-import android.hardware.display.DisplayManagerInternal;
-
-import com.android.server.display.DisplayBrightnessState;
-
-import java.io.PrintWriter;
-
-/**
- * An interface to define the general skeleton of how a BrightnessModeStrategy should look like
- * This is responsible for deciding the DisplayBrightnessState that the display should change to,
- * not taking into account clamping that might be needed
- */
-public interface DisplayBrightnessModeStrategy {
- /**
- * Decides the DisplayBrightnessState that the system should change to.
- *
- * @param displayPowerRequest The request to evaluate the updated brightness
- * @param displayState The target displayState to which the system should
- * change to after processing the request
- * @param displayBrightnessStateBuilder The DisplayBrightnessStateBuilder, consisting of
- * DisplayBrightnessState that have been constructed so far
- */
- DisplayBrightnessState.Builder updateBrightness(
- DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int displayState,
- DisplayBrightnessState.Builder displayBrightnessStateBuilder);
-
- /**
- * Used to dump the state.
- *
- * @param writer The PrintWriter used to dump the state.
- */
- void dump(PrintWriter writer);
-}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
new file mode 100644
index 0000000..27d04fd
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -0,0 +1,43 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+import android.annotation.NonNull;
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+/**
+ * Decides the DisplayBrighntessState that the display should change to based on strategy-specific
+ * logic within each implementation. Clamping should be done outside of DisplayBrightnessStrategy if
+ * not an integral part of the strategy.
+ */
+public interface DisplayBrightnessStrategy {
+ /**
+ * Decides the DisplayBrightnessState that the system should change to.
+ *
+ * @param displayPowerRequest The request to evaluate the updated brightness
+ */
+ DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest);
+
+ /**
+ * Returns the name of the Strategy
+ */
+ @NonNull
+ String getName();
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
new file mode 100644
index 0000000..c8b2c83
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+/**
+ * Manages the brightness of the display when the system is in the doze state.
+ */
+public class DozeBrightnessStrategy implements DisplayBrightnessStrategy {
+
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ // Todo(brup): Introduce a validator class and add validations before setting the brightness
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_DOZE);
+ return new DisplayBrightnessState.Builder()
+ .setBrightness(displayPowerRequest.dozeScreenBrightness)
+ .setSdrBrightness(displayPowerRequest.dozeScreenBrightness)
+ .setBrightnessReason(brightnessReason)
+ .build();
+ }
+
+ @Override
+ public String getName() {
+ return "DozeBrightnessStrategy";
+ }
+
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
new file mode 100644
index 0000000..f6ddf4f
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+/**
+ * Manages the brightness of the display when the system is in the invalid state.
+ */
+public class InvalidBrightnessStrategy implements DisplayBrightnessStrategy {
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.set(null);
+ return new DisplayBrightnessState.Builder()
+ .setBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT)
+ .setSdrBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT)
+ .setBrightnessReason(brightnessReason)
+ .build();
+ }
+
+ @Override
+ public String getName() {
+ return "InvalidBrightnessStrategy";
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
new file mode 100644
index 0000000..4138513
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+/**
+ * Manages the brightness of the display when the system is in the ScreenOff state.
+ */
+public class ScreenOffBrightnessStrategy implements DisplayBrightnessStrategy {
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ // Todo(brup): Introduce a validator class and add validations before setting the brightness
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF);
+ return new DisplayBrightnessState.Builder()
+ .setBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT)
+ .setSdrBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT)
+ .setBrightnessReason(brightnessReason)
+ .build();
+ }
+
+ @Override
+ public String getName() {
+ return "ScreenOffBrightnessStrategy";
+ }
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 87327cb..ea09629 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -81,6 +81,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Service api for managing dreams.
@@ -116,10 +117,14 @@
private final DreamUiEventLogger mDreamUiEventLogger;
private final ComponentName mAmbientDisplayComponent;
private final boolean mDismissDreamOnActivityStart;
- private final boolean mDreamsOnlyEnabledForSystemUser;
+ private final boolean mDreamsOnlyEnabledForDockUser;
private final boolean mDreamsEnabledByDefaultConfig;
private final boolean mDreamsActivatedOnChargeByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
+ private final boolean mKeepDreamingWhenUndockedDefault;
+
+ private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener>
+ mDreamManagerStateListeners = new CopyOnWriteArrayList<>();
@GuardedBy("mLock")
private DreamRecord mCurrentDream;
@@ -214,8 +219,8 @@
mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
- mDreamsOnlyEnabledForSystemUser =
- mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser);
+ mDreamsOnlyEnabledForDockUser =
+ mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForDockUser);
mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
R.bool.config_dismissDreamOnActivityStart);
@@ -226,6 +231,8 @@
mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
mSettingsObserver = new SettingsObserver(mHandler);
+ mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_keepDreamingWhenUndocking);
}
@Override
@@ -292,15 +299,14 @@
pw.println();
pw.println("mCurrentDream=" + mCurrentDream);
pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("mDreamsOnlyEnabledForDockUser=" + mDreamsOnlyEnabledForDockUser);
pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting);
- pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault);
pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault);
pw.println("mIsDocked=" + mIsDocked);
pw.println("mIsCharging=" + mIsCharging);
pw.println("mWhenToDream=" + mWhenToDream);
+ pw.println("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault);
pw.println("getDozeComponent()=" + getDozeComponent());
pw.println();
@@ -329,7 +335,16 @@
}
}
- /** Whether a real dream is occurring. */
+ private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+ mHandler.post(() -> {
+ for (DreamManagerInternal.DreamManagerStateListener listener
+ : mDreamManagerStateListeners) {
+ listener.onKeepDreamingWhenUndockedChanged(keepDreaming);
+ }
+ });
+ }
+
+ /** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDream != null && !mCurrentDream.isPreview
@@ -491,10 +506,6 @@
}
}
- private ComponentName getActiveDreamComponentInternal(boolean doze) {
- return chooseDreamForUser(doze, ActivityManager.getCurrentUser());
- }
-
/**
* If doze is true, returns the doze component for the user.
* Otherwise, returns the system dream component, if present.
@@ -576,6 +587,7 @@
}
mSystemDreamComponent = componentName;
+ reportKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
// Switch dream if currently dreaming and not dozing.
if (isDreamingInternal() && !isDozingInternal()) {
@@ -585,6 +597,10 @@
}
}
+ private boolean shouldKeepDreamingWhenUndocked() {
+ return mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null;
+ }
+
private ComponentName getDefaultDreamComponentForUser(int userId) {
String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
@@ -606,7 +622,8 @@
}
private boolean dreamsEnabledForUser(int userId) {
- return !mDreamsOnlyEnabledForSystemUser || (userId == UserHandle.USER_SYSTEM);
+ // TODO(b/257333623): Support non-system Dock Users in HSUM.
+ return !mDreamsOnlyEnabledForDockUser || (userId == UserHandle.USER_SYSTEM);
}
private ServiceInfo getServiceInfo(ComponentName name) {
@@ -647,7 +664,7 @@
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, DREAM_WAKE_LOCK_TAG);
final Binder dreamToken = mCurrentDream.token;
mHandler.post(wakeLock.wrap(() -> {
- mAtmInternal.notifyDreamStateChanged(true);
+ mAtmInternal.notifyActiveDreamChanged(name);
mController.startDream(dreamToken, name, isPreviewMode, canDoze, userId, wakeLock,
mDreamOverlayServiceName, reason);
}));
@@ -672,7 +689,7 @@
@GuardedBy("mLock")
private void cleanupDreamLocked() {
- mHandler.post(() -> mAtmInternal.notifyDreamStateChanged(false /*dreaming*/));
+ mHandler.post(() -> mAtmInternal.notifyActiveDreamChanged(null));
if (mCurrentDream == null) {
return;
@@ -1013,13 +1030,20 @@
}
@Override
- public ComponentName getActiveDreamComponent(boolean doze) {
- return getActiveDreamComponentInternal(doze);
+ public void requestDream() {
+ requestDreamInternal();
}
@Override
- public void requestDream() {
- requestDreamInternal();
+ public void registerDreamManagerStateListener(DreamManagerStateListener listener) {
+ mDreamManagerStateListeners.add(listener);
+ // Initialize the listener's state.
+ listener.onKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
+ }
+
+ @Override
+ public void unregisterDreamManagerStateListener(DreamManagerStateListener listener) {
+ mDreamManagerStateListeners.remove(listener);
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 2817d1b..28dc318 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -26,6 +26,7 @@
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.SystemFonts;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.ResultReceiver;
import android.os.SharedMemory;
@@ -35,8 +36,10 @@
import android.util.AndroidException;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.graphics.fonts.IFontManager;
import com.android.internal.security.VerityUtils;
@@ -47,7 +50,9 @@
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.DirectByteBuffer;
@@ -155,9 +160,30 @@
}
private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil {
+
+ private final String[] mDerCertPaths;
+
+ FsverityUtilImpl(String[] derCertPaths) {
+ mDerCertPaths = derCertPaths;
+ }
+
@Override
- public boolean hasFsverity(String filePath) {
- return VerityUtils.hasFsverity(filePath);
+ public boolean isFromTrustedProvider(String fontPath, byte[] pkcs7Signature) {
+ final byte[] digest = VerityUtils.getFsverityDigest(fontPath);
+ if (digest == null) {
+ Log.w(TAG, "Failed to get fs-verity digest for " + fontPath);
+ return false;
+ }
+ for (String certPath : mDerCertPaths) {
+ try (InputStream is = new FileInputStream(certPath)) {
+ if (VerityUtils.verifyPkcs7DetachedSignature(pkcs7Signature, digest, is)) {
+ return true;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to read certificate file: " + certPath);
+ }
+ }
+ return false;
}
@Override
@@ -175,11 +201,15 @@
@NonNull
private final Context mContext;
+ private final boolean mIsSafeMode;
+
private final Object mUpdatableFontDirLock = new Object();
+ private String mDebugCertFilePath = null;
+
@GuardedBy("mUpdatableFontDirLock")
@Nullable
- private final UpdatableFontDir mUpdatableFontDir;
+ private UpdatableFontDir mUpdatableFontDir;
// mSerializedFontMapLock can be acquired while holding mUpdatableFontDirLock.
// mUpdatableFontDirLock should not be newly acquired while holding mSerializedFontMapLock.
@@ -195,22 +225,43 @@
UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
}
mContext = context;
- mUpdatableFontDir = createUpdatableFontDir(safeMode);
+ mIsSafeMode = safeMode;
initialize();
}
@Nullable
- private static UpdatableFontDir createUpdatableFontDir(boolean safeMode) {
+ private UpdatableFontDir createUpdatableFontDir() {
// Never read updatable font files in safe mode.
- if (safeMode) return null;
+ if (mIsSafeMode) return null;
// If apk verity is supported, fs-verity should be available.
if (!VerityUtils.isFsVeritySupported()) return null;
+
+ String[] certs = mContext.getResources().getStringArray(
+ R.array.config_fontManagerServiceCerts);
+
+ if (mDebugCertFilePath != null && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+ String[] tmp = new String[certs.length + 1];
+ System.arraycopy(certs, 0, tmp, 0, certs.length);
+ tmp[certs.length] = mDebugCertFilePath;
+ certs = tmp;
+ }
+
return new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser(),
- new FsverityUtilImpl(), new File(CONFIG_XML_FILE));
+ new FsverityUtilImpl(certs), new File(CONFIG_XML_FILE));
+ }
+
+ /**
+ * Add debug certificate to the cert list. This must be called only on userdebug/eng
+ * build.
+ * @param debugCertPath a debug certificate file path
+ */
+ public void addDebugCertificate(@Nullable String debugCertPath) {
+ mDebugCertFilePath = debugCertPath;
}
private void initialize() {
synchronized (mUpdatableFontDirLock) {
+ mUpdatableFontDir = createUpdatableFontDir();
if (mUpdatableFontDir == null) {
setSerializedFontMap(serializeSystemServerFontMap());
return;
@@ -233,12 +284,12 @@
/* package */ void update(int baseVersion, List<FontUpdateRequest> requests)
throws SystemFontException {
- if (mUpdatableFontDir == null) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
- "The font updater is disabled.");
- }
synchronized (mUpdatableFontDirLock) {
+ if (mUpdatableFontDir == null) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
+ "The font updater is disabled.");
+ }
// baseVersion == -1 only happens from shell command. This is filtered and treated as
// error from SystemApi call.
if (baseVersion != -1 && mUpdatableFontDir.getConfigVersion() != baseVersion) {
@@ -273,10 +324,10 @@
}
/* package */ Map<String, File> getFontFileMap() {
- if (mUpdatableFontDir == null) {
- return Collections.emptyMap();
- }
synchronized (mUpdatableFontDirLock) {
+ if (mUpdatableFontDir == null) {
+ return Collections.emptyMap();
+ }
return mUpdatableFontDir.getPostScriptMap();
}
}
@@ -302,10 +353,10 @@
* Returns an active system font configuration.
*/
public @NonNull FontConfig getSystemFontConfig() {
- if (mUpdatableFontDir == null) {
- return SystemFonts.getSystemPreinstalledFontConfig();
- }
synchronized (mUpdatableFontDirLock) {
+ if (mUpdatableFontDir == null) {
+ return SystemFonts.getSystemPreinstalledFontConfig();
+ }
return mUpdatableFontDir.getSystemFontConfig();
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 88145bd..4cd0d6e 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -28,6 +28,7 @@
import android.graphics.fonts.FontVariationAxis;
import android.graphics.fonts.SystemFonts;
import android.os.Binder;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ShellCommand;
@@ -103,6 +104,10 @@
w.println("update-family [family definition XML path]");
w.println(" Update font families with the new definitions.");
w.println();
+ w.println("install-debug-cert [cert file path]");
+ w.println(" Install debug certificate file. This command can be used only on userdebug");
+ w.println(" or eng device with root user.");
+ w.println();
w.println("clear");
w.println(" Remove all installed font files and reset to the initial state.");
w.println();
@@ -322,6 +327,33 @@
return 0;
}
+ private int installCert(ShellCommand shell) throws SystemFontException {
+ if (!(Build.IS_USERDEBUG || Build.IS_ENG)) {
+ throw new SecurityException("Only userdebug/eng device can add debug certificate");
+ }
+ if (Binder.getCallingUid() != Process.ROOT_UID) {
+ throw new SecurityException("Only root can add debug certificate");
+ }
+
+ String certPath = shell.getNextArg();
+ if (certPath == null) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_INVALID_DEBUG_CERTIFICATE,
+ "Cert file path argument is required.");
+ }
+ File file = new File(certPath);
+ if (!file.isFile()) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_INVALID_DEBUG_CERTIFICATE,
+ "Cert file (" + file + ") is not found");
+ }
+
+ mService.addDebugCertificate(certPath);
+ mService.restart();
+ shell.getOutPrintWriter().println("Success");
+ return 0;
+ }
+
private int update(ShellCommand shell) throws SystemFontException {
String fontPath = shell.getNextArg();
if (fontPath == null) {
@@ -494,6 +526,8 @@
return restart(shell);
case "status":
return status(shell);
+ case "install-debug-cert":
+ return installCert(shell);
default:
return shell.handleDefaultCommands(cmd);
}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 743b4d9..457d5b7 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -40,6 +40,8 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
@@ -59,6 +61,8 @@
private static final String TAG = "UpdatableFontDir";
private static final String RANDOM_DIR_PREFIX = "~~";
+ private static final String FONT_SIGNATURE_FILE = "font.fsv_sig";
+
/** Interface to mock font file access in tests. */
interface FontFileParser {
String getPostScriptName(File file) throws IOException;
@@ -72,7 +76,7 @@
/** Interface to mock fs-verity in tests. */
interface FsverityUtil {
- boolean hasFsverity(String path);
+ boolean isFromTrustedProvider(String path, byte[] pkcs7Signature);
void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException;
@@ -188,12 +192,35 @@
FileUtils.deleteContentsAndDir(dir);
continue;
}
+
+ File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
+ if (!signatureFile.exists()) {
+ Slog.i(TAG, "The signature file is missing.");
+ FileUtils.deleteContentsAndDir(dir);
+ continue;
+ }
+ byte[] signature;
+ try {
+ signature = Files.readAllBytes(Paths.get(signatureFile.getAbsolutePath()));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read signature file.");
+ return;
+ }
+
File[] files = dir.listFiles();
- if (files == null || files.length != 1) {
+ if (files == null || files.length != 2) {
Slog.e(TAG, "Unexpected files in dir: " + dir);
return;
}
- FontFileInfo fontFileInfo = validateFontFile(files[0]);
+
+ File fontFile;
+ if (files[0].equals(signatureFile)) {
+ fontFile = files[1];
+ } else {
+ fontFile = files[0];
+ }
+
+ FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
if (fontConfig == null) {
fontConfig = getSystemFontConfig();
}
@@ -359,9 +386,25 @@
} catch (ErrnoException e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
- "Failed to change mode to 711", e);
+ "Failed to change font file mode to 644", e);
}
- FontFileInfo fontFileInfo = validateFontFile(newFontFile);
+ File signatureFile = new File(newDir, FONT_SIGNATURE_FILE);
+ try (FileOutputStream out = new FileOutputStream(signatureFile)) {
+ out.write(pkcs7Signature);
+ } catch (IOException e) {
+ // TODO: Do we need new error code for signature write failure?
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+ "Failed to write font signature file to storage.", e);
+ }
+ try {
+ Os.chmod(signatureFile.getAbsolutePath(), 0600);
+ } catch (ErrnoException e) {
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+ "Failed to change the signature file mode to 600", e);
+ }
+ FontFileInfo fontFileInfo = validateFontFile(newFontFile, pkcs7Signature);
// Try to create Typeface and treat as failure something goes wrong.
try {
@@ -478,8 +521,9 @@
* is higher than the currently used font.
*/
@NonNull
- private FontFileInfo validateFontFile(File file) throws SystemFontException {
- if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) {
+ private FontFileInfo validateFontFile(File file, byte[] pkcs7Signature)
+ throws SystemFontException {
+ if (!mFsverityUtil.isFromTrustedProvider(file.getAbsolutePath(), pkcs7Signature)) {
throw new SystemFontException(
FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
"Font validation failed. Fs-verity is not enabled: " + file);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9bce471f..8a22ab9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -837,7 +837,7 @@
void enableAudioReturnChannel(boolean enabled) {
assertRunOnServiceThread();
HdmiDeviceInfo avr = getAvrDeviceInfo();
- if (avr != null) {
+ if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) {
mService.enableAudioReturnChannel(avr.getPortId(), enabled);
}
}
@@ -1336,19 +1336,31 @@
}
@ServiceThreadOnly
+ private void forceDisableArcOnAllPins() {
+ List<HdmiPortInfo> ports = mService.getPortInfo();
+ for (HdmiPortInfo port : ports) {
+ if (isArcFeatureEnabled(port.getId())) {
+ mService.enableAudioReturnChannel(port.getId(), false);
+ }
+ }
+ }
+
+ @ServiceThreadOnly
private void disableArcIfExist() {
assertRunOnServiceThread();
HdmiDeviceInfo avr = getAvrDeviceInfo();
if (avr == null) {
return;
}
- disableArc();
// Seq #44.
removeAllRunningArcAction();
if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
}
+
+ // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time
+ forceDisableArcOnAllPins();
}
@ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 573bf19..5646e1b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -20,6 +20,8 @@
import static com.android.server.hdmi.Constants.ADDR_BACKUP_2;
import static com.android.server.hdmi.Constants.ADDR_TV;
+import static java.util.Map.entry;
+
import android.annotation.Nullable;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -45,7 +47,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -57,38 +58,34 @@
private static final String TAG = "HdmiUtils";
- private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE =
- new HashMap<Integer, List<Integer>>() {
- {
- put(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV));
- put(Constants.ADDR_RECORDER_1,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
- put(Constants.ADDR_RECORDER_2,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
- put(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_PLAYBACK_1,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
- put(Constants.ADDR_AUDIO_SYSTEM,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM));
- put(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_PLAYBACK_2,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
- put(Constants.ADDR_RECORDER_3,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
- put(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_PLAYBACK_3,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
- put(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
- HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
- HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
- put(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
- HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
- HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
- put(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV));
- put(Constants.ADDR_UNREGISTERED, Collections.emptyList());
- }
- };
+ private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE = Map.ofEntries(
+ entry(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV)),
+ entry(Constants.ADDR_RECORDER_1,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+ entry(Constants.ADDR_RECORDER_2,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+ entry(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_PLAYBACK_1,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+ entry(Constants.ADDR_AUDIO_SYSTEM,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)),
+ entry(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_PLAYBACK_2,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+ entry(Constants.ADDR_RECORDER_3,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+ entry(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_PLAYBACK_3,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+ entry(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+ HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+ HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+ entry(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+ HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+ HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+ entry(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV)),
+ entry(Constants.ADDR_UNREGISTERED, Collections.emptyList()));
private static final String[] DEFAULT_NAMES = {
"TV",
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 9d4f181..c83fa2d 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -32,6 +32,7 @@
import android.os.UEventObserver;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.view.InputDevice;
@@ -382,24 +383,28 @@
}
}
- public void dump(PrintWriter pw, String prefix) {
+ public void dump(PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
synchronized (mLock) {
- final String indent = prefix + " ";
- final String indent2 = indent + " ";
-
- pw.println(prefix + TAG + ":");
- pw.println(indent + "State: Polling = " + mIsPolling
+ ipw.println(TAG + ":");
+ ipw.increaseIndent();
+ ipw.println("State: Polling = " + mIsPolling
+ ", Interactive = " + mIsInteractive);
- pw.println(indent + "Listeners: " + mListenerRecords.size() + " battery listeners");
+ ipw.println("Listeners: " + mListenerRecords.size() + " battery listeners");
+ ipw.increaseIndent();
for (int i = 0; i < mListenerRecords.size(); i++) {
- pw.println(indent2 + i + ": " + mListenerRecords.valueAt(i));
+ ipw.println(i + ": " + mListenerRecords.valueAt(i));
}
+ ipw.decreaseIndent();
- pw.println(indent + "Device Monitors: " + mDeviceMonitors.size() + " monitors");
+ ipw.println("Device Monitors: " + mDeviceMonitors.size() + " monitors");
+ ipw.increaseIndent();
for (int i = 0; i < mDeviceMonitors.size(); i++) {
- pw.println(indent2 + i + ": " + mDeviceMonitors.valueAt(i));
+ ipw.println(i + ": " + mDeviceMonitors.valueAt(i));
}
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 7eb5a10..298098a 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -17,10 +17,15 @@
package com.android.server.input;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.graphics.PointF;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
import android.view.InputChannel;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import java.util.List;
@@ -142,6 +147,20 @@
public abstract void pilferPointers(IBinder token);
/**
+ * Called when the current input method and/or {@link InputMethodSubtype} is updated.
+ *
+ * @param userId User ID to be notified about.
+ * @param subtypeHandle A {@link InputMethodSubtypeHandle} corresponds to {@code subtype}.
+ * @param subtype A {@link InputMethodSubtype} object, or {@code null} when the current
+ * {@link InputMethodSubtype} is not suitable for the physical keyboard layout
+ * mapping.
+ * @see InputMethodSubtype#isSuitableForPhysicalKeyboardLayoutMapping()
+ */
+ public abstract void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+ @Nullable InputMethodSubtypeHandle subtypeHandle,
+ @Nullable InputMethodSubtype subtype);
+
+ /**
* Increments keyboard backlight level if the device has an associated keyboard backlight
* {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
*/
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 31f63d8..8497dfb 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -23,6 +23,7 @@
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.NotificationManager;
@@ -91,6 +92,7 @@
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -110,11 +112,13 @@
import android.view.SurfaceControl;
import android.view.VerifiedInputEvent;
import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
@@ -302,9 +306,9 @@
private final AdditionalDisplayInputProperties mCurrentDisplayProperties =
new AdditionalDisplayInputProperties();
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
- private int mIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
- private PointerIcon mIcon;
+ private PointerIcon mPointerIcon;
// Holds all the registered gesture monitors that are implemented as spy windows. The spy
// windows are mapped by their InputChannel tokens.
@@ -1810,8 +1814,8 @@
*/
public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken) {
- Objects.nonNull(fromChannelToken);
- Objects.nonNull(toChannelToken);
+ Objects.requireNonNull(fromChannelToken);
+ Objects.requireNonNull(toChannelToken);
return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
false /* isDragDrop */);
}
@@ -2325,12 +2329,12 @@
throw new IllegalArgumentException("Use setCustomPointerIcon to set custom pointers");
}
synchronized (mAdditionalDisplayInputPropertiesLock) {
- mIcon = null;
- mIconType = iconType;
+ mPointerIcon = null;
+ mPointerIconType = iconType;
if (!mCurrentDisplayProperties.pointerIconVisible) return;
- mNative.setPointerIconType(mIconType);
+ mNative.setPointerIconType(mPointerIconType);
}
}
@@ -2339,12 +2343,12 @@
public void setCustomPointerIcon(PointerIcon icon) {
Objects.requireNonNull(icon);
synchronized (mAdditionalDisplayInputPropertiesLock) {
- mIconType = PointerIcon.TYPE_CUSTOM;
- mIcon = icon;
+ mPointerIconType = PointerIcon.TYPE_CUSTOM;
+ mPointerIcon = icon;
if (!mCurrentDisplayProperties.pointerIconVisible) return;
- mNative.setCustomPointerIcon(mIcon);
+ mNative.setCustomPointerIcon(mPointerIcon);
}
}
@@ -2676,80 +2680,95 @@
@EnforcePermission(Manifest.permission.BLUETOOTH)
@Override
public String getInputDeviceBluetoothAddress(int deviceId) {
+ super.getInputDeviceBluetoothAddress_enforcePermission();
+
return mNative.getBluetoothAddress(deviceId);
}
+ @EnforcePermission(Manifest.permission.MONITOR_INPUT)
+ @Override
+ public void pilferPointers(IBinder inputChannelToken) {
+ super.pilferPointers_enforcePermission();
+
+ Objects.requireNonNull(inputChannelToken);
+ mNative.pilferPointers(inputChannelToken);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- pw.println("INPUT MANAGER (dumpsys input)\n");
+ ipw.println("INPUT MANAGER (dumpsys input)\n");
String dumpStr = mNative.dump();
if (dumpStr != null) {
pw.println(dumpStr);
}
- pw.println("Input Manager Service (Java) State:");
- dumpAssociations(pw, " " /*prefix*/);
- dumpSpyWindowGestureMonitors(pw, " " /*prefix*/);
- dumpDisplayInputPropertiesValues(pw, " " /*prefix*/);
- mBatteryController.dump(pw, " " /*prefix*/);
- mKeyboardBacklightController.dump(pw, " " /*prefix*/);
+ ipw.println("Input Manager Service (Java) State:");
+ ipw.increaseIndent();
+ dumpAssociations(ipw);
+ dumpSpyWindowGestureMonitors(ipw);
+ dumpDisplayInputPropertiesValues(ipw);
+ mBatteryController.dump(ipw);
+ mKeyboardBacklightController.dump(ipw);
}
- private void dumpAssociations(PrintWriter pw, String prefix) {
+ private void dumpAssociations(IndentingPrintWriter pw) {
if (!mStaticAssociations.isEmpty()) {
- pw.println(prefix + "Static Associations:");
+ pw.println("Static Associations:");
mStaticAssociations.forEach((k, v) -> {
- pw.print(prefix + " port: " + k);
+ pw.print(" port: " + k);
pw.println(" display: " + v);
});
}
synchronized (mAssociationsLock) {
if (!mRuntimeAssociations.isEmpty()) {
- pw.println(prefix + "Runtime Associations:");
+ pw.println("Runtime Associations:");
mRuntimeAssociations.forEach((k, v) -> {
- pw.print(prefix + " port: " + k);
+ pw.print(" port: " + k);
pw.println(" display: " + v);
});
}
if (!mUniqueIdAssociations.isEmpty()) {
- pw.println(prefix + "Unique Id Associations:");
+ pw.println("Unique Id Associations:");
mUniqueIdAssociations.forEach((k, v) -> {
- pw.print(prefix + " port: " + k);
+ pw.print(" port: " + k);
pw.println(" uniqueId: " + v);
});
}
}
}
- private void dumpSpyWindowGestureMonitors(PrintWriter pw, String prefix) {
+ private void dumpSpyWindowGestureMonitors(IndentingPrintWriter pw) {
synchronized (mInputMonitors) {
if (mInputMonitors.isEmpty()) return;
- pw.println(prefix + "Gesture Monitors (implemented as spy windows):");
+ pw.println("Gesture Monitors (implemented as spy windows):");
int i = 0;
for (final GestureMonitorSpyWindow monitor : mInputMonitors.values()) {
- pw.append(prefix + " " + i++ + ": ").println(monitor.dump());
+ pw.append(" " + i++ + ": ").println(monitor.dump());
}
}
}
- private void dumpDisplayInputPropertiesValues(PrintWriter pw, String prefix) {
+ private void dumpDisplayInputPropertiesValues(IndentingPrintWriter pw) {
synchronized (mAdditionalDisplayInputPropertiesLock) {
if (mAdditionalDisplayInputProperties.size() != 0) {
- pw.println(prefix + "mAdditionalDisplayInputProperties:");
+ pw.println("mAdditionalDisplayInputProperties:");
+ pw.increaseIndent();
for (int i = 0; i < mAdditionalDisplayInputProperties.size(); i++) {
- pw.println(prefix + " displayId: "
+ pw.println("displayId: "
+ mAdditionalDisplayInputProperties.keyAt(i));
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.valueAt(i);
- pw.println(prefix + " pointerAcceleration: " + properties.pointerAcceleration);
- pw.println(prefix + " pointerIconVisible: " + properties.pointerIconVisible);
+ pw.println("pointerAcceleration: " + properties.pointerAcceleration);
+ pw.println("pointerIconVisible: " + properties.pointerIconVisible);
}
+ pw.decreaseIndent();
}
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
- pw.println(prefix + "mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId);
+ pw.println("mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId);
}
}
}
@@ -3780,6 +3799,16 @@
}
@Override
+ public void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+ @Nullable InputMethodSubtypeHandle subtypeHandle,
+ @Nullable InputMethodSubtype subtype) {
+ if (DEBUG) {
+ Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+ + " subtypeHandle=" + subtypeHandle);
+ }
+ }
+
+ @Override
public void incrementKeyboardBacklight(int deviceId) {
mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
}
@@ -3839,11 +3868,11 @@
if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) {
mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible;
if (properties.pointerIconVisible) {
- if (mIconType == PointerIcon.TYPE_CUSTOM) {
- Objects.requireNonNull(mIcon);
- mNative.setCustomPointerIcon(mIcon);
+ if (mPointerIconType == PointerIcon.TYPE_CUSTOM) {
+ Objects.requireNonNull(mPointerIcon);
+ mNative.setCustomPointerIcon(mPointerIcon);
} else {
- mNative.setPointerIconType(mIconType);
+ mNative.setPointerIconType(mPointerIconType);
}
} else {
mNative.setPointerIconType(PointerIcon.TYPE_NULL);
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index e33f28c..b207e27 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -24,6 +24,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -216,12 +217,14 @@
return null;
}
- void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
+ void dump(PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
+ ipw.increaseIndent();
for (int i = 0; i < mKeyboardBacklights.size(); i++) {
Light light = mKeyboardBacklights.get(i);
- pw.println(prefix + " " + i + ": { id: " + light.getId() + ", name: " + light.getName()
- + " }");
+ ipw.println(i + ": { id: " + light.getId() + ", name: " + light.getName() + " }");
}
+ ipw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 8e6452b..4b040fa 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -58,6 +58,7 @@
private static final String NODE_IMI = "imi";
private static final String ATTR_ID = "id";
private static final String ATTR_LABEL = "label";
+ private static final String ATTR_NAME_OVERRIDE = "nameOverride";
private static final String ATTR_ICON = "icon";
private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
@@ -161,6 +162,7 @@
}
out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
+ out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
subtype.getLanguageTag());
@@ -243,6 +245,8 @@
}
final int icon = parser.getAttributeInt(null, ATTR_ICON);
final int label = parser.getAttributeInt(null, ATTR_LABEL);
+ final String untranslatableName = parser.getAttributeValue(null,
+ ATTR_NAME_OVERRIDE);
final String imeSubtypeLocale =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
final String languageTag =
@@ -258,6 +262,7 @@
final InputMethodSubtype.InputMethodSubtypeBuilder
builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(label)
+ .setSubtypeNameOverride(untranslatableName)
.setSubtypeIconResId(icon)
.setSubtypeLocale(imeSubtypeLocale)
.setLanguageTag(languageTag)
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index a4830be..1c7294f 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -18,9 +18,12 @@
import static android.view.InputDevice.SOURCE_STYLUS;
+import android.Manifest;
import android.annotation.AnyThread;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UiThread;
+import android.hardware.input.InputManager;
import android.os.IBinder;
import android.os.Looper;
import android.util.Slog;
@@ -141,6 +144,7 @@
* input events and disposing the input event receiver.
* @return the handwriting session to send to the IME, or null if the request was invalid.
*/
+ @RequiresPermission(Manifest.permission.MONITOR_INPUT)
@UiThread
@Nullable
HandwritingSession startHandwritingSession(
@@ -169,7 +173,7 @@
}
if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId);
- mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken());
+ InputManager.getInstance().pilferPointers(mHandwritingSurface.getInputChannel().getToken());
// Stop processing more events.
mHandwritingEventReceiver.dispose();
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 1a0f6f7..015e576 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -28,6 +28,7 @@
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
@@ -198,9 +199,10 @@
// TODO(b/192412909): Convert this back to void method
@AnyThread
- boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+ boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+ ResultReceiver resultReceiver) {
try {
- mTarget.showSoftInput(showInputToken, flags, resultReceiver);
+ mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
} catch (RemoteException e) {
logRemoteException(e);
return false;
@@ -210,9 +212,10 @@
// TODO(b/192412909): Convert this back to void method
@AnyThread
- boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+ boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+ ResultReceiver resultReceiver) {
try {
- mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
+ mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver);
} catch (RemoteException e) {
logRemoteException(e);
return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4d1c5ae..8b083bd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -129,6 +129,7 @@
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
@@ -151,6 +152,7 @@
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.IInputMethodSession;
@@ -162,6 +164,7 @@
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
@@ -641,6 +644,10 @@
*/
private boolean mInputShown;
+ /** The token tracking the current IME request or {@code null} otherwise. */
+ @Nullable
+ private ImeTracker.Token mCurStatsToken;
+
/**
* {@code true} if the current input method is in fullscreen mode.
*/
@@ -760,7 +767,7 @@
* <dd>
* If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
* </dd>
- * dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+ * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
* <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
* currently invisible.
* </dd>
@@ -784,8 +791,7 @@
/**
* Internal state snapshot when
- * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IRemoteInputConnection, EditorInfo,
- * boolean)} is about to be called.
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
*
* <p>Calling that IPC endpoint basically means that
* {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1070,7 +1076,7 @@
/**
* Add a new entry and discard the oldest entry as needed.
- * @param info {@lin StartInputInfo} to be added.
+ * @param info {@link StartInputInfo} to be added.
*/
void addEntry(@NonNull StartInputInfo info) {
final int index = mNextIndex;
@@ -1188,18 +1194,18 @@
} else if (accessibilityRequestingNoImeUri.equals(uri)) {
final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId);
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0 /* def */, mUserId);
mAccessibilityRequestingNoSoftKeyboard =
(accessibilitySoftKeyboardSetting & AccessibilityService.SHOW_MODE_MASK)
== AccessibilityService.SHOW_MODE_HIDDEN;
if (mAccessibilityRequestingNoSoftKeyboard) {
final boolean showRequested = mShowRequested;
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
mShowRequested = showRequested;
} else if (mShowRequested) {
- showCurrentInputLocked(mCurFocusedWindow,
- InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(mCurFocusedWindow,
SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
}
} else {
@@ -1665,8 +1671,8 @@
}
// Hide soft input before user switch task since switch task may block main handler a while
// and delayed the hideCurrentInputLocked().
- hideCurrentInputLocked(
- mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER);
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
mUserSwitchHandlerTask = task;
@@ -2221,7 +2227,7 @@
}
final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
try {
- client.asBinder().linkToDeath(deathRecipient, 0);
+ client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -2246,7 +2252,7 @@
synchronized (ImfLock.class) {
ClientState cs = mClients.remove(client.asBinder());
if (cs != null) {
- client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0);
+ client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
clearClientSessionLocked(cs);
clearClientSessionForAccessibilityLocked(cs);
@@ -2259,8 +2265,8 @@
}
if (mCurClient == cs) {
- hideCurrentInputLocked(
- mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
if (mBoundToMethod) {
mBoundToMethod = false;
IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -2307,6 +2313,8 @@
mCurClient.mSessionRequestedForAccessibility = false;
mCurClient = null;
mCurVirtualDisplayToScreenMatrix = null;
+ ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ mCurStatsToken = null;
mMenuController.hideInputMethodMenuLocked();
}
@@ -2379,8 +2387,11 @@
navButtonFlags, mCurImeDispatcher);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
- showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
- SoftInputShowHideReason.ATTACH_NEW_INPUT);
+ // Re-use current statsToken, if it exists.
+ final ImeTracker.Token statsToken = mCurStatsToken;
+ mCurStatsToken = null;
+ showCurrentInputLocked(mCurFocusedWindow, statsToken, getAppShowFlagsLocked(),
+ null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
String curId = getCurIdLocked();
@@ -2499,7 +2510,8 @@
if (mDisplayIdToShowIme == INVALID_DISPLAY) {
mImeHiddenByDisplayPolicy = true;
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
return InputBindResult.NO_IME;
}
@@ -3201,6 +3213,18 @@
}
@GuardedBy("ImfLock.class")
+ private void notifyInputMethodSubtypeChangedLocked(@UserIdInt int userId,
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+ final InputMethodSubtype normalizedSubtype =
+ subtype != null && subtype.isSuitableForPhysicalKeyboardLayoutMapping()
+ ? subtype : null;
+ final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
+ ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
+ mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
+ userId, newSubtypeHandle, normalizedSubtype);
+ }
+
+ @GuardedBy("ImfLock.class")
void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
@@ -3209,8 +3233,10 @@
// See if we need to notify a subtype change within the same IME.
if (id.equals(getSelectedMethodIdLocked())) {
+ final int userId = mSettings.getCurrentUserId();
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
+ notifyInputMethodSubtypeChangedLocked(userId, info, null);
return;
}
final InputMethodSubtype oldSubtype = mCurrentSubtype;
@@ -3225,6 +3251,7 @@
if (newSubtype == null || oldSubtype == null) {
Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+ ", new subtype = " + newSubtype);
+ notifyInputMethodSubtypeChangedLocked(userId, info, null);
return;
}
if (newSubtype != oldSubtype) {
@@ -3262,22 +3289,23 @@
}
@Override
- public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
- int lastClickTooType, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
int uid = Binder.getCallingUid();
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput");
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "showSoftInput")) {
+ if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return false;
}
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
- return showCurrentInputLocked(
- windowToken, flags, lastClickTooType, resultReceiver, reason);
+ return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType,
+ resultReceiver, reason);
} finally {
Binder.restoreCallingIdentity(ident);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3294,7 +3322,8 @@
"InputMethodManagerService#startStylusHandwriting");
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) {
+ if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
+ null /* statsToken */)) {
return;
}
if (!hasSupportedStylusLocked()) {
@@ -3349,19 +3378,33 @@
}
@GuardedBy("ImfLock.class")
- boolean showCurrentInputLocked(IBinder windowToken, int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- return showCurrentInputLocked(
- windowToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
+ boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ return showCurrentInputLocked(windowToken, statsToken, flags,
+ MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
}
@GuardedBy("ImfLock.class")
- private boolean showCurrentInputLocked(IBinder windowToken, int flags, int lastClickToolType,
+ private boolean showCurrentInputLocked(IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ // Create statsToken is none exists.
+ if (statsToken == null) {
+ String packageName = null;
+ if (mCurEditorInfo != null) {
+ packageName = mCurEditorInfo.packageName;
+ }
+ statsToken = new ImeTracker.Token(packageName);
+ ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT,
+ reason);
+ }
+
mShowRequested = true;
if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
if ((flags & InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
@@ -3371,8 +3414,10 @@
}
if (!mSystemReady) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
mBindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3380,6 +3425,9 @@
// create a placeholder token for IMS so that IMS cannot inject windows into client app.
Binder showInputToken = new Binder();
mShowRequestWindowMap.put(showInputToken, windowToken);
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+ mCurStatsToken = null;
final int showFlags = getImeShowFlagsLocked();
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
@@ -3391,23 +3439,34 @@
curMethod.updateEditorToolType(lastClickToolType);
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
- if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
+ if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
onShowHideSoftInputRequested(true /* show */, windowToken, reason);
}
mInputShown = true;
return true;
+ } else {
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ mCurStatsToken = statsToken;
}
return false;
}
@Override
- public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
int uid = Binder.getCallingUid();
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput");
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "hideSoftInput")) {
+ if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
+ if (mInputShown) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ } else {
+ ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ }
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -3415,7 +3474,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
- flags, resultReceiver, reason);
+ statsToken, flags, resultReceiver, reason);
} finally {
Binder.restoreCallingIdentity(ident);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3424,17 +3483,32 @@
}
@GuardedBy("ImfLock.class")
- boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ // Create statsToken is none exists.
+ if (statsToken == null) {
+ String packageName = null;
+ if (mCurEditorInfo != null) {
+ packageName = mCurEditorInfo.packageName;
+ }
+ statsToken = new ImeTracker.Token(packageName);
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
+ }
+
if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+
if (mShowForced && (flags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
// There is a chance that IMM#hideSoftInput() is called in a transient state where
// IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
@@ -3445,8 +3519,8 @@
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
IInputMethodInvoker curMethod = getCurMethodLocked();
- final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
- || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+ final boolean shouldHideSoftInput = (curMethod != null)
+ && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
boolean res;
if (shouldHideSoftInput) {
final Binder hideInputToken = new Binder();
@@ -3455,17 +3529,20 @@
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
+ ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
- if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
+ if (curMethod.hideSoftInput(hideInputToken, statsToken, 0 /* flags */,
+ resultReceiver)) {
onShowHideSoftInputRequested(false /* show */, windowToken, reason);
}
res = true;
} else {
+ ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
res = false;
}
mBindingController.setCurrentMethodNotVisible();
@@ -3473,6 +3550,9 @@
mShowRequested = false;
mShowExplicitlyRequested = false;
mShowForced = false;
+ // Cancel existing statsToken for show IME as we got a hide request.
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ mCurStatsToken = null;
return res;
}
@@ -3630,8 +3710,8 @@
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(
- mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_INVALID_USER);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER);
return InputBindResult.INVALID_USER;
}
@@ -3687,7 +3767,7 @@
boolean didStart = false;
InputBindResult res = null;
- // We shows the IME when the system allows the IME focused target window to restore the
+ // We show the IME when the system allows the IME focused target window to restore the
// IME visibility (e.g. switching to the app task when last time the IME is visible).
// Note that we don't restore IME visibility for some cases (e.g. when the soft input
// state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
@@ -3699,7 +3779,7 @@
res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
- showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
return res;
}
@@ -3712,8 +3792,8 @@
// be behind any soft input window, so hide the
// soft input window if it is shown.
if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
- hideCurrentInputLocked(
- mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
// If focused display changed, we should unbind current method
@@ -3742,10 +3822,7 @@
imeDispatcher);
didStart = true;
}
- showCurrentInputLocked(
- windowToken,
- InputMethodManager.SHOW_IMPLICIT,
- null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
}
break;
@@ -3758,14 +3835,16 @@
case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
}
break;
case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
if (!sameWindowFocused) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
}
break;
@@ -3781,7 +3860,7 @@
imeDispatcher);
didStart = true;
}
- showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
} else {
Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
@@ -3802,7 +3881,7 @@
imeDispatcher);
didStart = true;
}
- showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
}
} else {
@@ -3824,7 +3903,8 @@
// an editor upon refocusing a window.
if (startInputByWinGainedFocus) {
if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
}
}
@@ -3838,7 +3918,8 @@
// 2) SOFT_INPUT_STATE_VISIBLE state without an editor
// 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
}
res = startInputUncheckedLocked(cs, inputContext,
@@ -3853,8 +3934,15 @@
}
@GuardedBy("ImfLock.class")
- private boolean canInteractWithImeLocked(
- int uid, IInputMethodClient client, String methodName) {
+ private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken,
+ @SoftInputShowHideReason int reason) {
+ showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT,
+ null /* resultReceiver */, reason);
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
+ @Nullable ImeTracker.Token statsToken) {
if (mCurClient == null || client == null
|| mCurClient.mClient.asBinder() != client.asBinder()) {
// We need to check if this is the current client with
@@ -3862,13 +3950,16 @@
// be made before input is started in it.
final ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
if (!isImeClientFocused(mCurFocusedWindow, cs)) {
Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
return false;
}
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return true;
}
@@ -4221,7 +4312,7 @@
final int curTokenDisplayId;
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(callingUid, client,
- "getInputMethodWindowVisibleHeight")) {
+ "getInputMethodWindowVisibleHeight", null /* statsToken */)) {
if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
@@ -4444,7 +4535,8 @@
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) {
+ if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
+ null /* statsToken */)) {
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -4471,7 +4563,8 @@
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) {
+ if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
+ null /* statsToken */)) {
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -4656,7 +4749,8 @@
}
@BinderThread
- private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) {
+ private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
+ @Nullable ImeTracker.Token statsToken) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
@@ -4664,13 +4758,22 @@
}
if (!setVisible) {
if (mCurClient != null) {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+
mWindowManagerInternal.hideIme(
mHideRequestWindowMap.get(windowToken),
- mCurClient.mSelfReportedDisplayId);
+ mCurClient.mSelfReportedDisplayId, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
}
} else {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// Send to window manager to show IME after IME layout finishes.
- mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken));
+ mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken),
+ statsToken);
}
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -4737,7 +4840,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- hideCurrentInputLocked(mLastImeTargetWindow, flags, null, reason);
+ hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+ null /* resultReceiver */, reason);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4754,7 +4858,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- showCurrentInputLocked(mLastImeTargetWindow, flags, null,
+ showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+ null /* resultReceiver */,
SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4845,7 +4950,8 @@
case MSG_HIDE_CURRENT_INPUT_METHOD:
synchronized (ImfLock.class) {
final @SoftInputShowHideReason int reason = (int) msg.obj;
- hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, reason);
}
return true;
@@ -5297,6 +5403,7 @@
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
+ notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype);
if (!setSubtypeOnly) {
// Set InputMethod here
@@ -6332,7 +6439,8 @@
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
if (userId == mSettings.getCurrentUserId()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
// Reset the current IME
@@ -6597,8 +6705,9 @@
@BinderThread
@Override
- public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible);
+ public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
+ @Nullable ImeTracker.Token statsToken) {
+ mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken);
}
@BinderThread
diff --git a/services/core/java/com/android/server/locales/OWNERS b/services/core/java/com/android/server/locales/OWNERS
index 4d93bff..e1e946b 100644
--- a/services/core/java/com/android/server/locales/OWNERS
+++ b/services/core/java/com/android/server/locales/OWNERS
@@ -2,3 +2,6 @@
pratyushmore@google.com
goldmanj@google.com
ankitavyas@google.com
+allenwtsu@google.com
+calvinpan@google.com
+joshhou@google.com
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index dcec0aa..2669d21 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -140,7 +140,9 @@
import com.android.server.location.provider.proxy.ProxyLocationProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -308,6 +310,10 @@
permissionManagerInternal.setLocationExtraPackagesProvider(
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationExtraPackageNames));
+
+ // TODO(b/241604546): properly handle this callback
+ LocalServices.getService(UserManagerInternal.class).addUserVisibilityListener(
+ (u, v) -> Slogf.i(TAG, "onUserVisibilityChanged(): %d -> %b", u, v));
}
@Nullable
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index 4f6d0d4..e46b8c0c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -523,9 +523,9 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder(100);
- TransactionRecord[] arr;
+ ContextHubServiceTransaction[] arr;
synchronized (this) {
- arr = mTransactionQueue.toArray(new TransactionRecord[0]);
+ arr = mTransactionQueue.toArray(new ContextHubServiceTransaction[0]);
}
for (int i = 0; i < arr.length; i++) {
sb.append(i + ": " + arr[i] + "\n");
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 1435016..77cd673 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -76,6 +76,8 @@
"ENABLE_PSDS_PERIODIC_DOWNLOAD";
private static final String CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL =
"ENABLE_ACTIVE_SIM_EMERGENCY_SUPL";
+ private static final String CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION =
+ "ENABLE_NI_SUPL_MESSAGE_INJECTION";
static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1";
static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2";
static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3";
@@ -218,6 +220,14 @@
}
/**
+ * Returns true if NI SUPL message injection is enabled; Returns false otherwise.
+ * Default false if not set.
+ */
+ boolean isNiSuplMessageInjectionEnabled() {
+ return getBooleanConfig(CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION, false);
+ }
+
+ /**
* Returns true if a long-term PSDS server is configured.
*/
boolean isLongTermPsdsServerConfigured() {
@@ -286,26 +296,24 @@
Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
}
- Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
- {
- put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
- put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
+ Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>();
- if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
- put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
- }
+ map.put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
+ map.put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
- put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
- put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
- GnssConfiguration::native_set_gnss_pos_protocol_select);
- put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
- GnssConfiguration::native_set_emergency_supl_pdn);
+ if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
+ map.put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
+ }
- if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
- put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
- }
- }
- };
+ map.put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
+ map.put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
+ GnssConfiguration::native_set_gnss_pos_protocol_select);
+ map.put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
+ GnssConfiguration::native_set_emergency_supl_pdn);
+
+ if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
+ map.put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
+ }
for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
String propertyName = entry.getKey();
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f637b8..6f6b1c9 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -84,6 +84,7 @@
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.provider.Settings;
+import android.provider.Telephony.Sms.Intents;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentity;
import android.telephony.CellIdentityGsm;
@@ -95,6 +96,7 @@
import android.telephony.CellInfoLte;
import android.telephony.CellInfoNr;
import android.telephony.CellInfoWcdma;
+import android.telephony.SmsMessage;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -107,6 +109,7 @@
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.HexDump;
import com.android.server.FgThread;
import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback;
import com.android.server.location.gnss.NtpTimeHelper.InjectNtpTimeCallback;
@@ -523,23 +526,31 @@
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
- if (action == null) {
- return;
- }
+ mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
- switch (action) {
- case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
- case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
- subscriptionOrCarrierConfigChanged();
- break;
- }
+ if (mNetworkConnectivityHandler.isNativeAgpsRilSupported()
+ && mGnssConfiguration.isNiSuplMessageInjectionEnabled()) {
+ // Listen to WAP PUSH NI SUPL message.
+ // See User Plane Location Protocol Candidate Version 3.0,
+ // OMA-TS-ULP-V3_0-20110920-C, Section 8.3 OMA Push.
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+ try {
+ intentFilter.addDataType("application/vnd.omaloc-supl-init");
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ Log.w(TAG, "Malformed SUPL init mime type");
}
- }, intentFilter, null, mHandler);
+ mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+
+ // Listen to MT SMS NI SUPL message.
+ // See User Plane Location Protocol Candidate Version 3.0,
+ // OMA-TS-ULP-V3_0-20110920-C, Section 8.4 MT SMS.
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+ intentFilter.addDataScheme("sms");
+ intentFilter.addDataAuthority("localhost", "7275");
+ mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+ }
mNetworkConnectivityHandler.registerNetworkCallbacks();
@@ -560,6 +571,80 @@
updateEnabled();
}
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
+ if (action == null) {
+ return;
+ }
+
+ switch (action) {
+ case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+ case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+ subscriptionOrCarrierConfigChanged();
+ break;
+ case Intents.WAP_PUSH_RECEIVED_ACTION:
+ case Intents.DATA_SMS_RECEIVED_ACTION:
+ injectSuplInit(intent);
+ break;
+ }
+ }
+ };
+
+ private void injectSuplInit(Intent intent) {
+ if (!isNfwLocationAccessAllowed()) {
+ Log.w(TAG, "Reject SUPL INIT as no NFW location access");
+ return;
+ }
+
+ int slotIndex = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+ SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+ if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ Log.e(TAG, "Invalid slot index");
+ return;
+ }
+
+ byte[] suplInit = null;
+ String action = intent.getAction();
+ if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
+ SmsMessage[] messages = Intents.getMessagesFromIntent(intent);
+ if (messages == null) {
+ Log.e(TAG, "Message does not exist in the intent");
+ return;
+ }
+ for (SmsMessage message : messages) {
+ suplInit = message.getUserData();
+ injectSuplInit(suplInit, slotIndex);
+ }
+ } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
+ suplInit = intent.getByteArrayExtra("data");
+ injectSuplInit(suplInit, slotIndex);
+ }
+ }
+
+ private void injectSuplInit(byte[] suplInit, int slotIndex) {
+ if (suplInit != null) {
+ if (DEBUG) {
+ Log.d(TAG, "suplInit = "
+ + HexDump.toHexString(suplInit) + " slotIndex = " + slotIndex);
+ }
+ mGnssNative.injectNiSuplMessageData(suplInit, suplInit.length , slotIndex);
+ }
+ }
+
+ private boolean isNfwLocationAccessAllowed() {
+ if (mGnssNative.isInEmergencySession()) {
+ return true;
+ }
+ if (mGnssVisibilityControl != null
+ && mGnssVisibilityControl.hasLocationPermissionEnabledProxyApps()) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Implements {@link InjectNtpTimeCallback#injectTime}
*/
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 02bdfd5..a7fffe2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -762,6 +762,10 @@
return APN_INVALID;
}
+ protected boolean isNativeAgpsRilSupported() {
+ return native_is_agps_ril_supported();
+ }
+
// AGPS support
private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 631dbbf..4e5e5f8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -437,6 +437,10 @@
return locationPermissionEnabledProxyApps;
}
+ public boolean hasLocationPermissionEnabledProxyApps() {
+ return getLocationPermissionEnabledProxyApps().length > 0;
+ }
+
private void handleNfwNotification(NfwNotification nfwNotification) {
if (DEBUG) Log.d(TAG, "Non-framework location access notification: " + nfwNotification);
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 2d015a5d..edb2e5b 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -989,6 +989,14 @@
mGnssHal.injectPsdsData(data, length, psdsType);
}
+ /**
+ * Injects NI SUPL message data into the GNSS HAL.
+ */
+ public void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+ Preconditions.checkState(mRegistered);
+ mGnssHal.injectNiSuplMessageData(data, length, slotIndex);
+ }
+
@NativeEntryPoint
void reportGnssServiceDied() {
// Not necessary to clear (and restore) binder identity since it runs on another thread.
@@ -1278,7 +1286,7 @@
}
@NativeEntryPoint
- boolean isInEmergencySession() {
+ public boolean isInEmergencySession() {
return Binder.withCleanCallingIdentity(
() -> mEmergencyHelper.isInEmergency(
TimeUnit.SECONDS.toMillis(mConfiguration.getEsExtensionSec())));
@@ -1507,6 +1515,10 @@
protected void injectPsdsData(byte[] data, int length, int psdsType) {
native_inject_psds_data(data, length, psdsType);
}
+
+ protected void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+ native_inject_ni_supl_message_data(data, length, slotIndex);
+ }
}
// basic APIs
@@ -1650,6 +1662,9 @@
private static native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
int lac, long cid, int tac, int pcid, int arfcn);
+ private static native void native_inject_ni_supl_message_data(byte[] data, int length,
+ int slotIndex);
+
// PSDS APIs
private static native boolean native_supports_psds();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 92b685c7..25e71e8 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -248,14 +248,14 @@
// Locking order is mUserCreationAndRemovalLock -> mSpManager.
private final Object mUserCreationAndRemovalLock = new Object();
- // These two arrays are only used at boot time. To save memory, they are set to null when
- // PHASE_BOOT_COMPLETED is reached.
+ // These two arrays are only used at boot time. To save memory, they are set to null near the
+ // end of the boot, when onThirdPartyAppsStarted() is called.
@GuardedBy("mUserCreationAndRemovalLock")
private SparseIntArray mEarlyCreatedUsers = new SparseIntArray();
@GuardedBy("mUserCreationAndRemovalLock")
private SparseIntArray mEarlyRemovedUsers = new SparseIntArray();
@GuardedBy("mUserCreationAndRemovalLock")
- private boolean mBootComplete;
+ private boolean mThirdPartyAppsStarted;
// Current password metrics for all secured users on the device. Updated when user unlocks the
// device or changes password. Removed when user is stopped.
@@ -297,16 +297,9 @@
@Override
public void onBootPhase(int phase) {
super.onBootPhase(phase);
- switch (phase) {
- case PHASE_ACTIVITY_MANAGER_READY:
- mLockSettingsService.migrateOldDataAfterSystemReady();
- mLockSettingsService.loadEscrowData();
- break;
- case PHASE_BOOT_COMPLETED:
- mLockSettingsService.bootCompleted();
- break;
- default:
- break;
+ if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+ mLockSettingsService.migrateOldDataAfterSystemReady();
+ mLockSettingsService.loadEscrowData();
}
}
@@ -749,8 +742,8 @@
* <p>
* This is primarily needed for users that were removed by Android 13 or earlier, which didn't
* guarantee removal of LSS state as it relied on the {@code ACTION_USER_REMOVED} intent. It is
- * also needed because {@link #removeUser()} delays requests to remove LSS state until the
- * {@code PHASE_BOOT_COMPLETED} boot phase, so they can be lost.
+ * also needed because {@link #removeUser()} delays requests to remove LSS state until Weaver is
+ * guaranteed to be available, so they can be lost.
* <p>
* Stale state is detected by checking whether the user serial number changed. This works
* because user serial numbers are never reused.
@@ -931,7 +924,9 @@
return success;
}
- private void bootCompleted() {
+ // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+ // It does any synthetic password related work that was delayed from earlier in the boot.
+ private void onThirdPartyAppsStarted() {
synchronized (mUserCreationAndRemovalLock) {
// Handle delayed calls to LSS.removeUser() and LSS.createNewUser().
for (int i = 0; i < mEarlyRemovedUsers.size(); i++) {
@@ -976,7 +971,7 @@
setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
}
- mBootComplete = true;
+ mThirdPartyAppsStarted = true;
}
}
@@ -2086,9 +2081,11 @@
public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
checkPasswordReadPermission();
+ final long identity = Binder.clearCallingIdentity();
try {
return doVerifyCredential(credential, userId, progressCallback, 0 /* flags */);
} finally {
+ Binder.restoreCallingIdentity(identity);
scheduleGc();
}
}
@@ -2304,14 +2301,14 @@
private void createNewUser(@UserIdInt int userId, int userSerialNumber) {
synchronized (mUserCreationAndRemovalLock) {
- // Before PHASE_BOOT_COMPLETED, don't actually create the synthetic password yet, but
- // rather automatically delay it to later. We do this because protecting the synthetic
+ // During early boot, don't actually create the synthetic password yet, but rather
+ // automatically delay it to later. We do this because protecting the synthetic
// password requires the Weaver HAL if the device supports it, and some devices don't
// make Weaver available until fairly late in the boot process. This logic ensures a
// consistent flow across all devices, regardless of their Weaver implementation.
- if (!mBootComplete) {
- Slogf.i(TAG, "Delaying locksettings state creation for user %d until boot complete",
- userId);
+ if (!mThirdPartyAppsStarted) {
+ Slogf.i(TAG, "Delaying locksettings state creation for user %d until third-party " +
+ "apps are started", userId);
mEarlyCreatedUsers.put(userId, userSerialNumber);
mEarlyRemovedUsers.delete(userId);
return;
@@ -2325,14 +2322,14 @@
private void removeUser(@UserIdInt int userId) {
synchronized (mUserCreationAndRemovalLock) {
- // Before PHASE_BOOT_COMPLETED, don't actually remove the LSS state yet, but rather
- // automatically delay it to later. We do this because deleting synthetic password
- // protectors requires the Weaver HAL if the device supports it, and some devices don't
- // make Weaver available until fairly late in the boot process. This logic ensures a
- // consistent flow across all devices, regardless of their Weaver implementation.
- if (!mBootComplete) {
- Slogf.i(TAG, "Delaying locksettings state removal for user %d until boot complete",
- userId);
+ // During early boot, don't actually remove the LSS state yet, but rather automatically
+ // delay it to later. We do this because deleting synthetic password protectors
+ // requires the Weaver HAL if the device supports it, and some devices don't make Weaver
+ // available until fairly late in the boot process. This logic ensures a consistent
+ // flow across all devices, regardless of their Weaver implementation.
+ if (!mThirdPartyAppsStarted) {
+ Slogf.i(TAG, "Delaying locksettings state removal for user %d until third-party " +
+ "apps are started", userId);
if (mEarlyCreatedUsers.indexOfKey(userId) >= 0) {
mEarlyCreatedUsers.delete(userId);
} else {
@@ -2634,9 +2631,8 @@
* protects the user's CE key with a key derived from the SP.
* <p>
* This is called just once in the lifetime of the user: at user creation time (possibly delayed
- * until {@code PHASE_BOOT_COMPLETED} to ensure that the Weaver HAL is available if the device
- * supports it), or when upgrading from Android 13 or earlier where users with no LSKF didn't
- * necessarily have an SP.
+ * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
+ * or earlier where users with no LSKF didn't necessarily have an SP.
*/
@GuardedBy("mSpManager")
@VisibleForTesting
@@ -3159,7 +3155,7 @@
pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
synchronized (mUserCreationAndRemovalLock) {
- pw.println("BootComplete: " + mBootComplete);
+ pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted);
}
}
@@ -3317,6 +3313,11 @@
private final class LocalService extends LockSettingsInternal {
@Override
+ public void onThirdPartyAppsStarted() {
+ LockSettingsService.this.onThirdPartyAppsStarted();
+ }
+
+ @Override
public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index d836df5..807ba3c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -309,6 +309,10 @@
}
private void writeFile(File path, byte[] data) {
+ writeFile(path, data, /* syncParentDir= */ true);
+ }
+
+ private void writeFile(File path, byte[] data, boolean syncParentDir) {
synchronized (mFileWriteLock) {
// Use AtomicFile to guarantee atomicity of the file write, including when an existing
// file is replaced with a new one. This method is usually used to create new files,
@@ -326,9 +330,11 @@
file.failWrite(out);
}
// For performance reasons, AtomicFile only syncs the file itself, not also the parent
- // directory. The latter must be done explicitly here, as some callers need a guarantee
- // that the file really exists on-disk when this returns.
- fsyncDirectory(path.getParentFile());
+ // directory. The latter must be done explicitly when requested here, as some callers
+ // need a guarantee that the file really exists on-disk when this returns.
+ if (syncParentDir) {
+ fsyncDirectory(path.getParentFile());
+ }
mCache.putFile(path, data);
}
}
@@ -378,10 +384,20 @@
}
}
+ /**
+ * Writes the synthetic password state file for the given user ID, protector ID, and state name.
+ * If the file already exists, then it is atomically replaced.
+ * <p>
+ * This doesn't sync the parent directory, and a result the new state file may be lost if the
+ * system crashes. The caller must call {@link syncSyntheticPasswordState()} afterwards to sync
+ * the parent directory if needed, preferably after batching up other state file creations for
+ * the same user. We do it this way because directory syncs are expensive on some filesystems.
+ */
public void writeSyntheticPasswordState(int userId, long protectorId, String name,
byte[] data) {
ensureSyntheticPasswordDirectoryForUser(userId);
- writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data);
+ writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data,
+ /* syncParentDir= */ false);
}
public byte[] readSyntheticPasswordState(int userId, long protectorId, String name) {
@@ -392,6 +408,13 @@
deleteFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
}
+ /**
+ * Ensures that all synthetic password state files for the user have really been saved to disk.
+ */
+ public void syncSyntheticPasswordState(int userId) {
+ fsyncDirectory(getSyntheticPasswordDirectoryForUser(userId));
+ }
+
public Map<Integer, List<Long>> listSyntheticPasswordProtectorsForAllUsers(String stateName) {
Map<Integer, List<Long>> result = new ArrayMap<>();
final UserManager um = UserManager.get(mContext);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3fd488e..73a16fd 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -619,12 +619,16 @@
/**
* Creates a new synthetic password (SP) for the given user.
- *
+ * <p>
* Any existing SID for the user is cleared.
- *
+ * <p>
* Also saves the escrow information necessary to re-generate the synthetic password under
* an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
* password escrow should be disabled completely on the given user.
+ * <p>
+ * {@link syncState()} is not called yet; the caller should create a protector afterwards, which
+ * handles this. This makes it so that all the user's initial SP state files, including the
+ * initial LSKF-based protector, are efficiently created with only a single {@link syncState()}.
*/
SyntheticPassword newSyntheticPassword(int userId) {
clearSidForUser(userId);
@@ -668,6 +672,7 @@
private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
saveState(SP_HANDLE_NAME, spHandle, NULL_PROTECTOR_ID, userId);
+ syncState(userId);
}
private boolean loadEscrowData(SyntheticPassword sp, int userId) {
@@ -677,6 +682,11 @@
return e0 != null && p1 != null;
}
+ /**
+ * Saves the escrow data for the synthetic password. The caller is responsible for calling
+ * {@link syncState()} afterwards, once the user's other initial synthetic password state files
+ * have been created.
+ */
private void saveEscrowData(SyntheticPassword sp, int userId) {
saveState(SP_E0_NAME, sp.mEncryptedEscrowSplit0, NULL_PROTECTOR_ID, userId);
saveState(SP_P1_NAME, sp.mEscrowSplit1, NULL_PROTECTOR_ID, userId);
@@ -708,6 +718,10 @@
return buffer.getInt();
}
+ /**
+ * Creates a file that stores the Weaver slot the protector is using. The caller is responsible
+ * for calling {@link syncState()} afterwards, once all the protector's files have been created.
+ */
private void saveWeaverSlot(int slot, long protectorId, int userId) {
ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
buffer.put(WEAVER_VERSION);
@@ -837,6 +851,7 @@
}
createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret,
sid, userId);
+ syncState(userId); // ensure the new files are really saved to disk
return protectorId;
}
@@ -996,6 +1011,7 @@
saveSecdiscardable(tokenHandle, tokenData.secdiscardableOnDisk, userId);
createSyntheticPasswordBlob(tokenHandle, getTokenBasedProtectorType(tokenData.mType), sp,
tokenData.aggregatedSecret, 0L, userId);
+ syncState(userId); // ensure the new files are really saved to disk
tokenMap.get(userId).remove(tokenHandle);
if (tokenData.mCallback != null) {
tokenData.mCallback.onEscrowTokenActivated(tokenHandle, userId);
@@ -1003,6 +1019,11 @@
return true;
}
+ /**
+ * Creates a synthetic password blob, i.e. the file that stores the encrypted synthetic password
+ * (or encrypted escrow secret) for a protector. The caller is responsible for calling
+ * {@link syncState()} afterwards, once all the protector's files have been created.
+ */
private void createSyntheticPasswordBlob(long protectorId, byte protectorType,
SyntheticPassword sp, byte[] protectorSecret, long sid, int userId) {
final byte[] spSecret;
@@ -1118,6 +1139,7 @@
// (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
pwd.credentialType = credential.getType();
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
+ syncState(userId);
synchronizeFrpPassword(pwd, 0, userId);
} else {
Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
@@ -1156,6 +1178,7 @@
if (result.syntheticPassword != null && !credential.isNone() &&
!hasPasswordMetrics(protectorId, userId)) {
savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId);
+ syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
}
return result;
}
@@ -1275,6 +1298,7 @@
+ blob.mProtectorType);
createSyntheticPasswordBlob(protectorId, blob.mProtectorType, result, protectorSecret,
sid, userId);
+ syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
}
return result;
}
@@ -1396,12 +1420,21 @@
return ArrayUtils.concat(data, secdiscardable);
}
+ /**
+ * Generates and writes the secdiscardable file for the given protector. The caller is
+ * responsible for calling {@link syncState()} afterwards, once all the protector's files have
+ * been created.
+ */
private byte[] createSecdiscardable(long protectorId, int userId) {
byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
saveSecdiscardable(protectorId, data, userId);
return data;
}
+ /**
+ * Writes the secdiscardable file for the given protector. The caller is responsible for
+ * calling {@link syncState()} afterwards, once all the protector's files have been created.
+ */
private void saveSecdiscardable(long protectorId, byte[] secdiscardable, int userId) {
saveState(SECDISCARDABLE_NAME, secdiscardable, protectorId, userId);
}
@@ -1445,6 +1478,11 @@
return VersionedPasswordMetrics.deserialize(decrypted).getMetrics();
}
+ /**
+ * Creates the password metrics file: the file associated with the LSKF-based protector that
+ * contains the encrypted metrics about the LSKF. The caller is responsible for calling
+ * {@link syncState()} afterwards if needed.
+ */
private void savePasswordMetrics(LockscreenCredential credential, SyntheticPassword sp,
long protectorId, int userId) {
final byte[] encrypted = SyntheticPasswordCrypto.encrypt(sp.deriveMetricsKey(),
@@ -1466,10 +1504,21 @@
return mStorage.readSyntheticPasswordState(userId, protectorId, stateName);
}
+ /**
+ * Persists the given synthetic password state for the given user ID and protector ID.
+ * <p>
+ * For performance reasons, this doesn't sync the user's synthetic password state directory. As
+ * a result, it doesn't guarantee that the file will really be present after a crash. If that
+ * is needed, call {@link syncState()} afterwards, preferably after batching up related updates.
+ */
private void saveState(String stateName, byte[] data, long protectorId, int userId) {
mStorage.writeSyntheticPasswordState(userId, protectorId, stateName, data);
}
+ private void syncState(int userId) {
+ mStorage.syncSyntheticPasswordState(userId);
+ }
+
private void destroyState(String stateName, long protectorId, int userId) {
mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index fdc5bab..497ed03 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -41,7 +42,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ILogAccessDialogCallback;
-import com.android.internal.app.LogAccessDialogActivity;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -62,6 +62,10 @@
public final class LogcatManagerService extends SystemService {
private static final String TAG = "LogcatManagerService";
private static final boolean DEBUG = false;
+ private static final String TARGET_PACKAGE_NAME = "com.android.systemui";
+ private static final String TARGET_ACTIVITY_NAME =
+ "com.android.systemui.logcat.LogAccessDialogActivity";
+ public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
/** How long to wait for the user to approve/decline before declining automatically */
@VisibleForTesting
@@ -442,6 +446,7 @@
mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS);
final Intent mIntent = createIntent(client);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mIntent.setComponent(new ComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME));
mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
}
@@ -536,13 +541,13 @@
* Create the Intent for LogAccessDialogActivity.
*/
public Intent createIntent(LogAccessClient client) {
- final Intent intent = new Intent(mContext, LogAccessDialogActivity.class);
+ final Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName);
intent.putExtra(Intent.EXTRA_UID, client.mUid);
- intent.putExtra(LogAccessDialogActivity.EXTRA_CALLBACK, mDialogCallback.asBinder());
+ intent.putExtra(EXTRA_CALLBACK, mDialogCallback.asBinder());
return intent;
}
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index dcdb881..72ce38b 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -275,6 +275,10 @@
String.valueOf(mComponentType));
}
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
@ComponentType
private static int getComponentType(PendingIntent pendingIntent) {
if (pendingIntent.isBroadcast()) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 0f6192a..c0340b1 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -189,10 +189,6 @@
// Binder call
@Override
public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
-
final int uid = Binder.getCallingUid();
if (!validatePackageName(uid, packageName)) {
throw new SecurityException("packageName must match the calling uid");
@@ -217,9 +213,6 @@
// Binder call
@Override
public void registerClientGroupId(IMediaRouterClient client, String groupId) {
- if (client == null) {
- throw new NullPointerException("client must not be null");
- }
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
!= PackageManager.PERMISSION_GRANTED) {
@@ -240,10 +233,6 @@
// Binder call
@Override
public void unregisterClient(IMediaRouterClient client) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
-
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -257,10 +246,6 @@
// Binder call
@Override
public MediaRouterClientState getState(IMediaRouterClient client) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
-
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -274,10 +259,6 @@
// Binder call
@Override
public boolean isPlaybackActive(IMediaRouterClient client) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
-
final long token = Binder.clearCallingIdentity();
try {
ClientRecord clientRecord;
@@ -314,10 +295,6 @@
@Override
public void setDiscoveryRequest(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
-
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -336,10 +313,6 @@
// selected route or a default selection.
@Override
public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
-
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -353,12 +326,7 @@
// Binder call
@Override
public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
- if (routeId == null) {
- throw new IllegalArgumentException("routeId must not be null");
- }
+ Objects.requireNonNull(routeId, "routeId must not be null");
final long token = Binder.clearCallingIdentity();
try {
@@ -373,12 +341,7 @@
// Binder call
@Override
public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
- if (client == null) {
- throw new IllegalArgumentException("client must not be null");
- }
- if (routeId == null) {
- throw new IllegalArgumentException("routeId must not be null");
- }
+ Objects.requireNonNull(routeId, "routeId must not be null");
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5dcbb16..d6b9bd5 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3797,13 +3797,13 @@
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList) {
- createNotificationChannelsImpl(pkg, uid, channelsList,
+ ParceledListSlice channelsList, boolean fromTargetApp) {
+ createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp,
ActivityTaskManager.INVALID_TASK_ID);
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList, int startingTaskId) {
+ ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
ParceledListSlice<NotificationChannel> oldChannels =
@@ -3815,7 +3815,7 @@
final NotificationChannel channel = channels.get(i);
Objects.requireNonNull(channel, "channel in list is null");
needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
- channel, true /* fromTargetApp */,
+ channel, fromTargetApp,
mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
if (needsPolicyFileChange) {
@@ -3851,6 +3851,7 @@
@Override
public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
+ boolean fromTargetApp = !isCallerSystemOrPhone(); // if not system, it's from the app
int taskId = ActivityTaskManager.INVALID_TASK_ID;
try {
int uid = mPackageManager.getPackageUid(pkg, 0,
@@ -3859,14 +3860,15 @@
} catch (RemoteException e) {
// Do nothing
}
- createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
+ createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp,
+ taskId);
}
@Override
public void createNotificationChannelsForPackage(String pkg, int uid,
ParceledListSlice channelsList) {
enforceSystemOrSystemUI("only system can call this");
- createNotificationChannelsImpl(pkg, uid, channelsList);
+ createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */);
}
@Override
@@ -3881,7 +3883,8 @@
CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
conversationChannel.setConversationId(parentId, conversationId);
createNotificationChannelsImpl(
- pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)),
+ false /* fromTargetApp */);
mRankingHandler.requestSort();
handleSavePolicyFile();
}
@@ -4967,10 +4970,10 @@
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
- // If the caller is system, take the package name from the rule's owner rather than
- // from the caller's package.
+ // If the calling app is the system (from any user), take the package name from the
+ // rule's owner rather than from the caller's package.
String rulePkg = pkg;
- if (isCallingUidSystem()) {
+ if (isCallingAppIdSystem()) {
if (automaticZenRule.getOwner() != null) {
rulePkg = automaticZenRule.getOwner().getPackageName();
}
@@ -9773,6 +9776,12 @@
return uid == Process.SYSTEM_UID;
}
+ protected boolean isCallingAppIdSystem() {
+ final int uid = Binder.getCallingUid();
+ final int appid = UserHandle.getAppId(uid);
+ return appid == Process.SYSTEM_UID;
+ }
+
protected boolean isUidSystemOrPhone(int uid) {
final int appid = UserHandle.getAppId(uid);
return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 12324bf..e6fd7ec 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -261,9 +261,11 @@
private boolean packageRequestsNotificationPermission(String packageName,
@UserIdInt int userId) {
try {
- String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS,
- userId).requestedPermissions;
- return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION);
+ PackageInfo pi = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, userId);
+ if (pi != null) {
+ String[] permissions = pi.requestedPermissions;
+ return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index bbbf452..444fef6 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -852,7 +852,9 @@
Objects.requireNonNull(pkg);
Objects.requireNonNull(group);
Objects.requireNonNull(group.getId());
- Objects.requireNonNull(!TextUtils.isEmpty(group.getName()));
+ if (TextUtils.isEmpty(group.getName())) {
+ throw new IllegalArgumentException("group.getName() can't be empty");
+ }
boolean needsDndChange = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
@@ -916,7 +918,7 @@
throw new IllegalArgumentException("Reserved id");
}
NotificationChannel existing = r.channels.get(channel.getId());
- if (existing != null && fromTargetApp) {
+ if (existing != null) {
// Actually modifying an existing channel - keep most of the existing settings
if (existing.isDeleted()) {
// The existing channel was deleted - undelete it.
@@ -1002,9 +1004,7 @@
}
if (fromTargetApp) {
channel.setLockscreenVisibility(r.visibility);
- channel.setAllowBubbles(existing != null
- ? existing.getAllowBubbles()
- : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE);
}
clearLockedFieldsLocked(channel);
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 5e98cc0..978e436 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Binder;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -64,7 +65,8 @@
private final IOverlayManager mInterface;
private static final Map<String, Integer> TYPE_MAP = Map.of(
"color", TypedValue.TYPE_FIRST_COLOR_INT,
- "string", TypedValue.TYPE_STRING);
+ "string", TypedValue.TYPE_STRING,
+ "drawable", -1);
OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
mContext = ctx;
@@ -258,7 +260,7 @@
String name = "";
String filename = null;
String opt;
- String configuration = null;
+ String config = null;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--user":
@@ -277,7 +279,7 @@
filename = getNextArgRequired();
break;
case "--config":
- configuration = getNextArgRequired();
+ config = getNextArgRequired();
break;
default:
err.println("Error: Unknown option: " + opt);
@@ -312,7 +314,9 @@
final String resourceName = getNextArgRequired();
final String typeStr = getNextArgRequired();
final String strData = String.join(" ", peekRemainingArgs());
- addOverlayValue(overlayBuilder, resourceName, typeStr, strData, configuration);
+ if (addOverlayValue(overlayBuilder, resourceName, typeStr, strData, config) != 0) {
+ return 1;
+ }
}
mInterface.commit(new OverlayManagerTransaction.Builder()
@@ -369,8 +373,10 @@
return 1;
}
String config = parser.getAttributeValue(null, "config");
- addOverlayValue(overlayBuilder, targetPackage + ':' + target,
- overlayType, value, config);
+ if (addOverlayValue(overlayBuilder, targetPackage + ':' + target,
+ overlayType, value, config) != 0) {
+ return 1;
+ }
}
}
}
@@ -384,7 +390,7 @@
return 0;
}
- private void addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
+ private int addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
String resourceName, String typeString, String valueString, String configuration) {
final int type;
typeString = typeString.toLowerCase(Locale.getDefault());
@@ -399,6 +405,9 @@
}
if (type == TypedValue.TYPE_STRING) {
overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
+ } else if (type < 0) {
+ ParcelFileDescriptor pfd = openFileForSystem(valueString, "r");
+ overlayBuilder.setResourceValue(resourceName, pfd, configuration);
} else {
final int intData;
if (valueString.startsWith("0x")) {
@@ -408,6 +417,7 @@
}
overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
}
+ return 0;
}
private int runEnableExclusive() throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 3b676c65..b4792c6 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -44,7 +44,6 @@
import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watched;
-import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedSparseBooleanMatrix;
@@ -179,9 +178,9 @@
@NonNull
@Watched
- protected WatchedArrayList<String> mProtectedBroadcasts;
+ protected WatchedArraySet<String> mProtectedBroadcasts;
@NonNull
- protected SnapshotCache<WatchedArrayList<String>> mProtectedBroadcastsSnapshot;
+ protected SnapshotCache<WatchedArraySet<String>> mProtectedBroadcastsSnapshot;
/**
* This structure maps uid -> uid and indicates whether access from the first should be
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 2e67bf2..5b837f1 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -73,7 +73,6 @@
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;
-import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedSparseBooleanMatrix;
import com.android.server.utils.WatchedSparseSetArray;
@@ -82,11 +81,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
* Implementation of the methods that update the internal structures of AppsFilter. Because of the
@@ -114,7 +110,7 @@
*/
@GuardedBy("mQueryableViaUsesPermissionLock")
@NonNull
- private HashMap<String, Set<Integer>> mPermissionToUids;
+ private final ArrayMap<String, ArraySet<Integer>> mPermissionToUids;
/**
* A cache that maps parsed {@link android.R.styleable#AndroidManifestUsesPermission
@@ -124,7 +120,7 @@
*/
@GuardedBy("mQueryableViaUsesPermissionLock")
@NonNull
- private HashMap<String, Set<Integer>> mUsesPermissionToUids;
+ private final ArrayMap<String, ArraySet<Integer>> mUsesPermissionToUids;
/**
* Ensures an observer is in the list, exactly once. The observer cannot be null. The
@@ -223,11 +219,11 @@
mForceQueryable = new WatchedArraySet<>();
mForceQueryableSnapshot = new SnapshotCache.Auto<>(
mForceQueryable, mForceQueryable, "AppsFilter.mForceQueryable");
- mProtectedBroadcasts = new WatchedArrayList<>();
+ mProtectedBroadcasts = new WatchedArraySet<>();
mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
- mPermissionToUids = new HashMap<>();
- mUsesPermissionToUids = new HashMap<>();
+ mPermissionToUids = new ArrayMap<>();
+ mUsesPermissionToUids = new ArrayMap<>();
mSnapshot = new SnapshotCache<AppsFilterSnapshot>(this, this) {
@Override
@@ -573,13 +569,17 @@
return null;
}
- final boolean protectedBroadcastsChanged;
- synchronized (mProtectedBroadcastsLock) {
- protectedBroadcastsChanged =
- mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts());
- }
- if (protectedBroadcastsChanged) {
- mQueriesViaComponentRequireRecompute.set(true);
+ final List<String> newBroadcasts = newPkg.getProtectedBroadcasts();
+ if (newBroadcasts.size() != 0) {
+ final boolean protectedBroadcastsChanged;
+ synchronized (mProtectedBroadcastsLock) {
+ final int oldSize = mProtectedBroadcasts.size();
+ mProtectedBroadcasts.addAll(newBroadcasts);
+ protectedBroadcastsChanged = mProtectedBroadcasts.size() != oldSize;
+ }
+ if (protectedBroadcastsChanged) {
+ mQueriesViaComponentRequireRecompute.set(true);
+ }
}
final boolean newIsForceQueryable;
@@ -606,7 +606,10 @@
// Lookup in the mPermissionToUids cache if installed packages have
// defined this permission.
if (mPermissionToUids.containsKey(usesPermissionName)) {
- for (int targetAppId : mPermissionToUids.get(usesPermissionName)) {
+ final ArraySet<Integer> permissionDefiners =
+ mPermissionToUids.get(usesPermissionName);
+ for (int j = 0; j < permissionDefiners.size(); j++) {
+ final int targetAppId = permissionDefiners.valueAt(j);
if (targetAppId != newPkgSetting.getAppId()) {
mQueryableViaUsesPermission.add(newPkgSetting.getAppId(),
targetAppId);
@@ -616,7 +619,7 @@
// Record in mUsesPermissionToUids that a permission was requested
// by a new package
if (!mUsesPermissionToUids.containsKey(usesPermissionName)) {
- mUsesPermissionToUids.put(usesPermissionName, new HashSet<>());
+ mUsesPermissionToUids.put(usesPermissionName, new ArraySet<>());
}
mUsesPermissionToUids.get(usesPermissionName).add(newPkgSetting.getAppId());
}
@@ -630,7 +633,10 @@
// Lookup in the mUsesPermissionToUids cache if installed packages have
// requested this permission.
if (mUsesPermissionToUids.containsKey(permissionName)) {
- for (int queryingAppId : mUsesPermissionToUids.get(permissionName)) {
+ final ArraySet<Integer> permissionUsers = mUsesPermissionToUids.get(
+ permissionName);
+ for (int j = 0; j < permissionUsers.size(); j++) {
+ final int queryingAppId = permissionUsers.valueAt(j);
if (queryingAppId != newPkgSetting.getAppId()) {
mQueryableViaUsesPermission.add(queryingAppId,
newPkgSetting.getAppId());
@@ -639,7 +645,7 @@
}
// Record in mPermissionToUids that a permission was defined by a new package
if (!mPermissionToUids.containsKey(permissionName)) {
- mPermissionToUids.put(permissionName, new HashSet<>());
+ mPermissionToUids.put(permissionName, new ArraySet<>());
}
mPermissionToUids.get(permissionName).add(newPkgSetting.getAppId());
}
@@ -1149,7 +1155,12 @@
final ArrayList<String> protectedBroadcasts = new ArrayList<>(
mProtectedBroadcasts.untrackedStorage());
collectProtectedBroadcasts(settings, removingPackageName);
- protectedBroadcastsChanged = !mProtectedBroadcasts.containsAll(protectedBroadcasts);
+ for (int i = 0; i < protectedBroadcasts.size(); ++i) {
+ if (!mProtectedBroadcasts.contains(protectedBroadcasts.get(i))) {
+ protectedBroadcastsChanged = true;
+ break;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 7daa0b9..483fa8a 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -29,7 +29,7 @@
import com.android.server.pm.pkg.component.ParsedIntentInfo;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.utils.WatchedArrayList;
+import com.android.server.utils.WatchedArraySet;
import java.util.List;
import java.util.Set;
@@ -45,7 +45,7 @@
/** Returns true if the querying package may query for the potential target package */
public static boolean canQueryViaComponents(AndroidPackage querying,
- AndroidPackage potentialTarget, WatchedArrayList<String> protectedBroadcasts) {
+ AndroidPackage potentialTarget, WatchedArraySet<String> protectedBroadcasts) {
if (!querying.getQueriesIntents().isEmpty()) {
for (Intent intent : querying.getQueriesIntents()) {
if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) {
@@ -117,7 +117,7 @@
}
private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget,
- WatchedArrayList<String> protectedBroadcasts) {
+ WatchedArraySet<String> protectedBroadcasts) {
if (matchesAnyComponents(
intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) {
return true;
@@ -138,7 +138,7 @@
private static boolean matchesAnyComponents(Intent intent,
List<? extends ParsedMainComponent> components,
- WatchedArrayList<String> protectedBroadcasts) {
+ WatchedArraySet<String> protectedBroadcasts) {
for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) {
ParsedMainComponent component = components.get(i);
if (!component.isExported()) {
@@ -152,7 +152,7 @@
}
private static boolean matchesAnyFilter(Intent intent, ParsedComponent component,
- WatchedArrayList<String> protectedBroadcasts) {
+ WatchedArraySet<String> protectedBroadcasts) {
List<ParsedIntentInfo> intents = component.getIntents();
for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
IntentFilter intentFilter = intents.get(i).getIntentFilter();
@@ -164,7 +164,7 @@
}
private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter,
- @Nullable WatchedArrayList<String> protectedBroadcasts) {
+ @Nullable WatchedArraySet<String> protectedBroadcasts) {
return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
intent.getData(), intent.getCategories(), "AppsFilter", true,
protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index d856d54..d72aacc 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -152,8 +152,6 @@
@GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_OK;
- @GuardedBy("mLock") private long mLastExecutionStartTimeMs;
- @GuardedBy("mLock") private long mLastExecutionDurationIncludingSleepMs;
@GuardedBy("mLock") private long mLastExecutionStartUptimeMs;
@GuardedBy("mLock") private long mLastExecutionDurationMs;
@@ -234,10 +232,6 @@
writer.println(mDisableJobSchedulerJobs);
writer.print("mLastExecutionStatus:");
writer.println(mLastExecutionStatus);
- writer.print("mLastExecutionStartTimeMs:");
- writer.println(mLastExecutionStartTimeMs);
- writer.print("mLastExecutionDurationIncludingSleepMs:");
- writer.println(mLastExecutionDurationIncludingSleepMs);
writer.print("mLastExecutionStartUptimeMs:");
writer.println(mLastExecutionStartUptimeMs);
writer.print("mLastExecutionDurationMs:");
@@ -564,8 +558,6 @@
private boolean runIdleOptimization(
PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate) {
synchronized (mLock) {
- mLastExecutionStartTimeMs = SystemClock.elapsedRealtime();
- mLastExecutionDurationIncludingSleepMs = -1;
mLastExecutionStartUptimeMs = SystemClock.uptimeMillis();
mLastExecutionDurationMs = -1;
}
@@ -574,8 +566,6 @@
logStatus(status);
synchronized (mLock) {
mLastExecutionStatus = status;
- mLastExecutionDurationIncludingSleepMs =
- SystemClock.elapsedRealtime() - mLastExecutionStartTimeMs;
mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
}
@@ -979,10 +969,9 @@
synchronized (mLock) {
status = mLastExecutionStatus;
durationMs = mLastExecutionDurationMs;
- durationIncludingSleepMs = mLastExecutionDurationIncludingSleepMs;
}
- mStatsLogger.write(status, params.getStopReason(), durationMs, durationIncludingSleepMs);
+ mStatsLogger.write(status, params.getStopReason(), durationMs);
}
/** Injector pattern for testing purpose */
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index bf00a33..5b8ee2b 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -125,6 +125,14 @@
ActivityInfo getActivityInfo(ComponentName component, long flags, int userId);
/**
+ * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+ * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+ * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+ * chained cross profiles
+ */
+ ActivityInfo getActivityInfoCrossProfile(ComponentName component, long flags, int userId);
+
+ /**
* Important: The provided filterCallingUid is used exclusively to filter out activities
* that can be seen based on user state. It's typically the original caller uid prior
* to clearing. Because it can only be provided by trusted code, its value can be
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b285136..a8534b0 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -835,6 +835,24 @@
}
/**
+ * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+ * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+ * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+ * chained cross profiles
+ * @param component application's component
+ * @param flags resolve info flags
+ * @param userId user id where activity resides
+ * @return ActivityInfo corresponding to requested component.
+ */
+ public final ActivityInfo getActivityInfoCrossProfile(ComponentName component,
+ @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ flags = updateFlagsForComponent(flags, userId);
+
+ return getActivityInfoInternalBody(component, flags, Binder.getCallingUid(), userId);
+ }
+
+ /**
* Important: The provided filterCallingUid is used exclusively to filter out activities
* that can be seen based on user state. It's typically the original caller uid prior
* to clearing. Because it can only be provided by trusted code, its value can be
@@ -1711,7 +1729,7 @@
ComponentName forwardingActivityComponentName = new ComponentName(
androidApplication().packageName, className);
ActivityInfo forwardingActivityInfo =
- getActivityInfo(forwardingActivityComponentName, 0,
+ getActivityInfoCrossProfile(forwardingActivityComponentName, 0,
sourceUserId);
if (!targetIsProfile) {
forwardingActivityInfo.showUserIcon = targetUserId;
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 798217f..04bd135 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -49,6 +49,15 @@
//flag to decide if intent needs to be resolved cross profile if pkgName is already defined
public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
+ /*
+ This flag, denotes if further cross profile resolution is allowed, e.g. if profile#0 is linked
+ to profile#1 and profile#2 . When intent resolution from profile#1 is started we resolve it in
+ profile#1 and profile#0. The profile#0 is also linked to profile#2, we will only resolve in
+ profile#2 if CrossProfileIntentFilter between profile#1 and profile#0 have set flag
+ FLAG_ALLOW_CHAINED_RESOLUTION.
+ */
+ public static final int FLAG_ALLOW_CHAINED_RESOLUTION = 0x00000010;
+
private static final String TAG = "CrossProfileIntentFilter";
/**
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 5ae4cab..4362956 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -36,14 +36,19 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.server.LocalServices;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationUtils;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Queue;
+import java.util.Set;
import java.util.function.Function;
/**
@@ -115,73 +120,111 @@
Intent intent, String resolvedType, int userId, long flags, String pkgName,
boolean hasNonNegativePriorityResult,
Function<String, PackageStateInternal> pkgSettingFunction) {
-
+ Queue<Integer> pendingUsers = new ArrayDeque<>();
+ Set<Integer> visitedUserIds = new HashSet<>();
+ SparseBooleanArray hasNonNegativePriorityResultFromParent = new SparseBooleanArray();
+ visitedUserIds.add(userId);
+ pendingUsers.add(userId);
+ hasNonNegativePriorityResultFromParent.put(userId, hasNonNegativePriorityResult);
+ UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
+ while (!pendingUsers.isEmpty()) {
+ int currentUserId = pendingUsers.poll();
+ List<CrossProfileIntentFilter> matchingFilters =
+ computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
+ currentUserId);
- List<CrossProfileIntentFilter> matchingFilters =
- computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
-
- if (matchingFilters == null || matchingFilters.isEmpty()) {
- /** if intent is web intent, checking if parent profile should handle the intent even
- if there is no matching filter. The configuration is based on user profile
- restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
- if (intent.hasWebURI()) {
- UserInfo parent = computer.getProfileParent(userId);
- if (parent != null) {
- CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
- .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags, userId,
- parent.id);
- if (generalizedCrossProfileDomainInfo != null) {
- crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
+ if (matchingFilters == null || matchingFilters.isEmpty()) {
+ /** if intent is web intent, checking if parent profile should handle the intent
+ * even if there is no matching filter. The configuration is based on user profile
+ * restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
+ if (currentUserId == userId && intent.hasWebURI()) {
+ UserInfo parent = computer.getProfileParent(currentUserId);
+ if (parent != null) {
+ CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
+ .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags,
+ currentUserId, parent.id);
+ if (generalizedCrossProfileDomainInfo != null) {
+ crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
+ }
}
}
+ continue;
}
- return crossProfileDomainInfos;
- }
- UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
- UserInfo sourceUserInfo = umInternal.getUserInfo(userId);
+ UserInfo sourceUserInfo = umInternal.getUserInfo(currentUserId);
- // Grouping the CrossProfileIntentFilters based on targerId
- SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
- new SparseArray<>();
+ // Grouping the CrossProfileIntentFilters based on targerId
+ SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
+ new SparseArray<>();
- for (int index = 0; index < matchingFilters.size(); index++) {
- CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
+ for (int index = 0; index < matchingFilters.size(); index++) {
+ CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
- if (!crossProfileIntentFiltersByUser
- .contains(crossProfileIntentFilter.mTargetUserId)) {
- crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
- new ArrayList<>());
+ if (!crossProfileIntentFiltersByUser
+ .contains(crossProfileIntentFilter.mTargetUserId)) {
+ crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
+ new ArrayList<>());
+ }
+ crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
+ .add(crossProfileIntentFilter);
}
- crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
- .add(crossProfileIntentFilter);
- }
- /*
- For each target user, we would call their corresponding strategy
- {@link CrossProfileResolver} to resolve intent in corresponding user
- */
- for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
+ /*
+ For each target user, we would call their corresponding strategy
+ {@link CrossProfileResolver} to resolve intent in corresponding user
+ */
+ for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
- UserInfo targetUserInfo = umInternal.getUserInfo(crossProfileIntentFiltersByUser
- .keyAt(index));
+ int targetUserId = crossProfileIntentFiltersByUser.keyAt(index);
- // Choosing strategy based on source and target user
- CrossProfileResolver crossProfileResolver =
- chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
+ //if user is already visited then skip resolution for particular user.
+ if (visitedUserIds.contains(targetUserId)) {
+ continue;
+ }
+
+ UserInfo targetUserInfo = umInternal.getUserInfo(targetUserId);
+
+ // Choosing strategy based on source and target user
+ CrossProfileResolver crossProfileResolver =
+ chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
/*
If {@link CrossProfileResolver} is available for source,target pair we will call it to
get {@link CrossProfileDomainInfo}s from that user.
*/
- if (crossProfileResolver != null) {
- List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
- .resolveIntent(computer, intent, resolvedType, userId,
- crossProfileIntentFiltersByUser.keyAt(index), flags, pkgName,
- crossProfileIntentFiltersByUser.valueAt(index),
- hasNonNegativePriorityResult, pkgSettingFunction);
- crossProfileDomainInfos.addAll(crossProfileInfos);
+ if (crossProfileResolver != null) {
+ List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
+ .resolveIntent(computer, intent, resolvedType, currentUserId,
+ targetUserId, flags, pkgName,
+ crossProfileIntentFiltersByUser.valueAt(index),
+ hasNonNegativePriorityResultFromParent.get(currentUserId),
+ pkgSettingFunction);
+ crossProfileDomainInfos.addAll(crossProfileInfos);
+
+ hasNonNegativePriorityResultFromParent.put(targetUserId,
+ hasNonNegativePriority(crossProfileInfos));
+
+ /*
+ Adding target user to queue if flag
+ {@link CrossProfileIntentFilter#FLAG_ALLOW_CHAINED_RESOLUTION} is set for any
+ {@link CrossProfileIntentFilter}
+ */
+ boolean allowChainedResolution = false;
+ for (int filterIndex = 0; filterIndex < crossProfileIntentFiltersByUser
+ .valueAt(index).size(); filterIndex++) {
+ if ((CrossProfileIntentFilter
+ .FLAG_ALLOW_CHAINED_RESOLUTION & crossProfileIntentFiltersByUser
+ .valueAt(index).get(filterIndex).mFlags) != 0) {
+ allowChainedResolution = true;
+ break;
+ }
+ }
+ if (allowChainedResolution) {
+ pendingUsers.add(targetUserId);
+ }
+ visitedUserIds.add(targetUserId);
+ }
}
}
@@ -237,7 +280,7 @@
/**
* Returns true if we source user can reach target user for given intent. The source can
- * directly or indirectly reach to target. This will perform depth first search to check if
+ * directly or indirectly reach to target. This will perform breadth first search to check if
* source can reach target.
* @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
* @param intent request
@@ -251,13 +294,38 @@
@UserIdInt int targetUserId) {
if (sourceUserId == targetUserId) return true;
- List<CrossProfileIntentFilter> matches =
- computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
- if (matches != null) {
- for (int index = 0; index < matches.size(); index++) {
- CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
- if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
- return true;
+ Queue<Integer> pendingUsers = new ArrayDeque<>();
+ Set<Integer> visitedUserIds = new HashSet<>();
+ visitedUserIds.add(sourceUserId);
+ pendingUsers.add(sourceUserId);
+
+ while (!pendingUsers.isEmpty()) {
+ int currentUserId = pendingUsers.poll();
+
+ List<CrossProfileIntentFilter> matches =
+ computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
+ currentUserId);
+ if (matches != null) {
+ for (int index = 0; index < matches.size(); index++) {
+ CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
+ if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
+ return true;
+ }
+ if (visitedUserIds.contains(crossProfileIntentFilter.mTargetUserId)) {
+ continue;
+ }
+
+ /*
+ If source cannot directly reach to target, we will add
+ CrossProfileIntentFilter.mTargetUserId user to queue to check if target user
+ can be reached via CrossProfileIntentFilter.mTargetUserId i.e. it can be
+ indirectly reached through chained/linked profiles.
+ */
+ if ((CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION
+ & crossProfileIntentFilter.mFlags) != 0) {
+ pendingUsers.add(crossProfileIntentFilter.mTargetUserId);
+ visitedUserIds.add(crossProfileIntentFilter.mTargetUserId);
+ }
}
}
}
@@ -605,4 +673,14 @@
return resolveInfoList;
}
+
+ /**
+ * @param crossProfileDomainInfos list of cross profile domain info in descending priority order
+ * @return if the list contains a resolve info with non-negative priority
+ */
+ private boolean hasNonNegativePriority(List<CrossProfileDomainInfo> crossProfileDomainInfos) {
+ return crossProfileDomainInfos.size() > 0
+ && crossProfileDomainInfos.get(0).mResolveInfo != null
+ && crossProfileDomainInfos.get(0).mResolveInfo.priority >= 0;
+ }
}
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index cac9323..ceaaefd 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -319,4 +319,135 @@
HOME,
MOBILE_NETWORK_SETTINGS);
}
+
+ /**
+ * Clone profile's DefaultCrossProfileIntentFilter
+ */
+
+ /*
+ Allowing media capture from clone to parent profile as clone profile would not have camera
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_MEDIA_CAPTURE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(MediaStore.ACTION_IMAGE_CAPTURE)
+ .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+ .addAction(MediaStore.ACTION_VIDEO_CAPTURE)
+ .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+ .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
+ .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+ .addAction(MediaStore.INTENT_ACTION_VIDEO_CAMERA)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .build();
+
+ /*
+ Allowing send action from clone to parent profile to share content from clone apps to parent
+ apps
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_SEND_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_SEND)
+ .addAction(Intent.ACTION_SEND_MULTIPLE)
+ .addAction(Intent.ACTION_SENDTO)
+ .addDataType("*/*")
+ .build();
+
+ /*
+ Allowing send action from parent to clone profile to share content from parent apps to clone
+ apps
+ */
+ private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_SEND_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_SEND)
+ .addAction(Intent.ACTION_SEND_MULTIPLE)
+ .addAction(Intent.ACTION_SENDTO)
+ .addDataType("*/*")
+ .build();
+
+ /*
+ Allowing view action from clone to parent profile to open any app-links or web links
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_VIEW_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_VIEW)
+ .addDataScheme("https")
+ .addDataScheme("http")
+ .build();
+
+ /*
+ Allowing view action from parent to clone profile to open any app-links or web links
+ */
+ private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_VIEW_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_VIEW)
+ .addDataScheme("https")
+ .addDataScheme("http")
+ .build();
+
+ /*
+ Allowing pick,insert and edit action from clone to parent profile to open picker or contacts
+ insert/edit.
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_PICK_INSERT_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_PICK)
+ .addAction(Intent.ACTION_GET_CONTENT)
+ .addAction(Intent.ACTION_EDIT)
+ .addAction(Intent.ACTION_INSERT)
+ .addAction(Intent.ACTION_INSERT_OR_EDIT)
+ .addDataType("*/*")
+ .build();
+
+ /*
+ Allowing pick,insert and edit action from parent to clone profile to open picker
+ */
+ private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_PICK_INSERT_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_PICK)
+ .addAction(Intent.ACTION_GET_CONTENT)
+ .addAction(Intent.ACTION_EDIT)
+ .addAction(Intent.ACTION_INSERT)
+ .addAction(Intent.ACTION_INSERT_OR_EDIT)
+ .addDataType("*/*")
+ .build();
+
+ public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
+ return Arrays.asList(
+ PARENT_TO_CLONE_SEND_ACTION,
+ PARENT_TO_CLONE_VIEW_ACTION,
+ PARENT_TO_CLONE_PICK_INSERT_ACTION,
+ CLONE_TO_PARENT_MEDIA_CAPTURE,
+ CLONE_TO_PARENT_SEND_ACTION,
+ CLONE_TO_PARENT_VIEW_ACTION,
+ CLONE_TO_PARENT_PICK_INSERT_ACTION
+
+ );
+ }
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 88a3f8e..095a7f6 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -261,6 +261,7 @@
final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
info.sendSystemPackageUpdatedBroadcasts();
+ PackageMetrics.onUninstallSucceeded(info, deleteFlags, mUserManagerInternal);
}
// Force a gc to clear up things.
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7ea0c04..70bd24c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -150,7 +150,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.InstallLocationUtils;
import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -427,7 +426,7 @@
mPm.snapshotComputer().checkPackageFrozen(pkgName);
}
- final boolean isReplace = request.isReplace();
+ final boolean isReplace = request.isInstallReplace();
// Also need to kill any apps that are dependent on the library, except the case of
// installation of new version static shared library.
if (clientLibPkgs != null) {
@@ -814,6 +813,7 @@
for (InstallRequest request : requests) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
+ request.onPrepareStarted();
preparePackageLI(request);
} catch (PrepareFailure prepareFailure) {
request.setError(prepareFailure.error,
@@ -822,6 +822,7 @@
request.setOriginPermission(prepareFailure.mConflictingPermission);
return;
} finally {
+ request.onPrepareFinished();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -834,11 +835,13 @@
request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
final String packageName = packageToScan.getPackageName();
try {
+ request.onScanStarted();
final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
request.getParseFlags(), request.getScanFlags(),
System.currentTimeMillis(), request.getUser(),
request.getAbiOverride());
request.setScanResult(scanResult);
+ request.onScanFinished();
if (!scannedPackages.add(packageName)) {
request.setError(
PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
@@ -871,7 +874,7 @@
}
}
- Map<String, ReconciledPackage> reconciledPackages;
+ List<ReconciledPackage> reconciledPackages;
synchronized (mPm.mLock) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
@@ -966,7 +969,7 @@
final boolean isRollback =
request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK;
@PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
- if (request.isMoveInstall()) {
+ if (request.isInstallMove()) {
// moving a complete application; perform an initial scan on the new install location
scanFlags |= SCAN_INITIAL;
}
@@ -1349,7 +1352,7 @@
}
}
- if (request.isMoveInstall()) {
+ if (request.isInstallMove()) {
// We did an in-place move, so dex is ready to roll
scanFlags |= SCAN_NO_DEX;
scanFlags |= SCAN_MOVE;
@@ -1399,7 +1402,7 @@
doRenameLI(request, parsedPackage);
try {
- setUpFsVerityIfPossible(parsedPackage);
+ setUpFsVerity(parsedPackage);
} catch (Installer.InstallerException | IOException | DigestException
| NoSuchAlgorithmException e) {
throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
@@ -1668,7 +1671,7 @@
ParsedPackage parsedPackage) throws PrepareFailure {
final int status = request.getReturnCode();
final String statusMsg = request.getReturnMsg();
- if (request.isMoveInstall()) {
+ if (request.isInstallMove()) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
request.getMovePackageName(), request.getMoveFromCodePath());
@@ -1796,13 +1799,10 @@
}
/**
- * Set up fs-verity for the given package if possible. This requires a feature flag of system
- * property to be enabled only if the kernel supports fs-verity.
- *
- * <p>When the feature flag is set to legacy mode, only APK is supported (with some experimental
- * kernel patches). In normal mode, all file format can be supported.
+ * Set up fs-verity for the given package. For older devices that do not support fs-verity,
+ * this is a no-op.
*/
- private void setUpFsVerityIfPossible(AndroidPackage pkg) throws Installer.InstallerException,
+ private void setUpFsVerity(AndroidPackage pkg) throws Installer.InstallerException,
PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
if (!PackageManagerServiceUtils.isApkVerityEnabled()) {
return;
@@ -1837,17 +1837,22 @@
}
for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
- final String filePath = entry.getKey();
- final String signaturePath = entry.getValue();
-
- // fs-verity is optional for now. Only set up if signature is provided.
- if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
- try {
- VerityUtils.setUpFsverity(filePath, signaturePath);
- } catch (IOException e) {
- throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
- "Failed to enable fs-verity: " + e);
+ try {
+ final String filePath = entry.getKey();
+ if (VerityUtils.hasFsverity(filePath)) {
+ continue;
}
+
+ // Set up fs-verity with optional signature.
+ final String signaturePath = entry.getValue();
+ String optionalSignaturePath = null;
+ if (new File(signaturePath).exists()) {
+ optionalSignaturePath = signaturePath;
+ }
+ VerityUtils.setUpFsverity(filePath, optionalSignaturePath);
+ } catch (IOException e) {
+ throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+ "Failed to enable fs-verity: " + e);
}
}
}
@@ -1877,18 +1882,19 @@
}
@GuardedBy("mPm.mLock")
- private void commitPackagesLocked(Map<String, ReconciledPackage> reconciledPackages,
+ private void commitPackagesLocked(List<ReconciledPackage> reconciledPackages,
@NonNull int[] allUsers) {
// TODO: remove any expected failures from this method; this should only be able to fail due
// to unavoidable errors (I/O, etc.)
- for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+ for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
final ParsedPackage parsedPackage = installRequest.getParsedPackage();
final String packageName = parsedPackage.getPackageName();
final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
- if (installRequest.isReplace()) {
+ installRequest.onCommitStarted();
+ if (installRequest.isInstallReplace()) {
AndroidPackage oldPackage = mPm.mPackages.get(packageName);
// Set the update and install times
@@ -1905,7 +1911,7 @@
mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
installRequest.getScannedPackageSetting(),
allUsers, mPm.mSettings.getPackagesLocked());
- if (installRequest.isSystem()) {
+ if (installRequest.isInstallSystem()) {
// Remove existing system package
removePackageHelper.removePackage(oldPackage, true);
if (!disableSystemPackageLPw(oldPackage)) {
@@ -1973,6 +1979,7 @@
mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
mPm.updateInstantAppInstallerLocked(packageName);
}
+ installRequest.onCommitFinished();
}
ApplicationPackageManager.invalidateGetPackagesForUidCache();
}
@@ -2197,9 +2204,9 @@
* locks on {@link com.android.server.pm.PackageManagerService.mLock}.
*/
@GuardedBy("mPm.mInstallLock")
- private void executePostCommitStepsLIF(Map<String, ReconciledPackage> reconciledPackages) {
+ private void executePostCommitStepsLIF(List<ReconciledPackage> reconciledPackages) {
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
- for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+ for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
@@ -2223,7 +2230,7 @@
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
| Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
- if (installRequest.isReplace()) {
+ if (installRequest.isInstallReplace()) {
mDexManager.notifyPackageUpdated(pkg.getPackageName(),
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
@@ -2330,45 +2337,6 @@
incrementalStorages);
}
- public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) {
- String packageName = pkgLite.packageName;
- int installLocation = pkgLite.installLocation;
- // reader
- synchronized (mPm.mLock) {
- // Currently installed package which the new package is attempting to replace or
- // null if no such package is installed.
- AndroidPackage installedPkg = mPm.mPackages.get(packageName);
-
- if (installedPkg != null) {
- if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
- // Check for updated system application.
- if (installedPkg.isSystem()) {
- return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
- } else {
- // If current upgrade specifies particular preference
- if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
- // Application explicitly specified internal.
- return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
- } else if (
- installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
- // App explicitly prefers external. Let policy decide
- } else {
- // Prefer previous location
- if (installedPkg.isExternalStorage()) {
- return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
- }
- return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
- }
- }
- } else {
- // Invalid install. Return error code
- return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
- }
- }
- }
- return pkgLite.recommendedInstallLocation;
- }
-
Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
long requiredInstalledVersionCode, int installFlags) {
if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
@@ -2651,11 +2619,29 @@
}
}
+ Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, request.getUid());
+ if (update) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, true);
+ }
+ extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+
+ // If a package is a static shared library, then only the installer of the package
+ // should get the broadcast.
+ if (installerPackageName != null
+ && request.getPkg().getStaticSharedLibraryName() != null) {
+ mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/,
+ installerPackageName, null /*finishedReceiver*/,
+ request.getNewUsers(), null /* instantUserIds*/,
+ null /* broadcastAllowList */, null);
+ }
+
// Send installed broadcasts if the package is not a static shared lib.
if (request.getPkg().getStaticSharedLibraryName() == null) {
mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
- // Send added for users that see the package for the first time
+ // Send PACKAGE_ADDED broadcast for users that see the package for the first time
// sendPackageAddedForNewUsers also deals with system apps
int appId = UserHandle.getAppId(request.getUid());
boolean isSystem = request.getPkg().isSystem();
@@ -2663,13 +2649,9 @@
isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
firstUserIds, firstInstantUserIds, dataLoaderType);
- // Send added for users that don't see the package for the first time
- Bundle extras = new Bundle();
- extras.putInt(Intent.EXTRA_UID, request.getUid());
- if (update) {
- extras.putBoolean(Intent.EXTRA_REPLACING, true);
- }
- extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+ // Send PACKAGE_ADDED broadcast for users that don't see
+ // the package for the first time
+
// Send to all running apps.
final SparseArray<int[]> newBroadcastAllowList;
synchronized (mPm.mLock) {
@@ -2682,8 +2664,8 @@
extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
updateUserIds, instantUserIds, newBroadcastAllowList, null);
+ // Send to the installer, even if it's not running.
if (installerPackageName != null) {
- // Send to the installer, even if it's not running.
mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
extras, 0 /*flags*/,
installerPackageName, null /*finishedReceiver*/,
@@ -3637,7 +3619,7 @@
boolean appIdCreated = false;
try {
final String pkgName = scanResult.mPkgSetting.getPackageName();
- final Map<String, ReconciledPackage> reconcileResult =
+ final List<ReconciledPackage> reconcileResult =
ReconcilePackageUtils.reconcilePackages(
Collections.singletonList(installRequest),
mPm.mPackages, Collections.singletonMap(pkgName,
@@ -3649,7 +3631,7 @@
} else {
installRequest.setScannedPackageSettingAppId(Process.INVALID_UID);
}
- commitReconciledScanResultLocked(reconcileResult.get(pkgName),
+ commitReconciledScanResultLocked(reconcileResult.get(0),
mPm.mUserManager.getUserIds());
} catch (PackageManagerException e) {
if (appIdCreated) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 573082a..4e5a6f9 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,10 +16,13 @@
package com.android.server.pm;
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.os.Process.INVALID_UID;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.TAG;
import android.annotation.NonNull;
@@ -57,8 +60,10 @@
@Nullable
private PackageRemovedInfo mRemovedInfo;
- private @PackageManagerService.ScanFlags int mScanFlags;
- private @ParsingPackageUtils.ParseFlags int mParseFlags;
+ @PackageManagerService.ScanFlags
+ private int mScanFlags;
+ @ParsingPackageUtils.ParseFlags
+ private int mParseFlags;
private boolean mReplace;
@Nullable /* The original Package if it is being replaced, otherwise {@code null} */
@@ -105,6 +110,14 @@
@Nullable
private ScanResult mScanResult;
+ private boolean mIsInstallInherit;
+ private boolean mIsInstallForUsers;
+
+ @Nullable
+ private final PackageMetrics mPackageMetrics;
+ private final int mSessionId;
+ private final int mRequireUserAction;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -116,6 +129,10 @@
params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
params.mDataLoaderType, params.mPackageSource);
+ mPackageMetrics = new PackageMetrics(this);
+ mIsInstallInherit = params.mIsInherit;
+ mSessionId = params.mSessionId;
+ mRequireUserAction = params.mRequireUserAction;
}
// Install existing package as user
@@ -127,6 +144,10 @@
mPkg = pkg;
mNewUsers = newUsers;
mPostInstallRunnable = runnable;
+ mPackageMetrics = new PackageMetrics(this);
+ mIsInstallForUsers = true;
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
// addForInit
@@ -143,6 +164,9 @@
mParseFlags = parseFlags;
mScanFlags = scanFlags;
mScanResult = scanResult;
+ mPackageMetrics = null; // No logging from this code path
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
@Nullable
@@ -200,7 +224,7 @@
return mInstallArgs == null ? null : mInstallArgs.mObserver;
}
- public boolean isMoveInstall() {
+ public boolean isInstallMove() {
return mInstallArgs != null && mInstallArgs.mMoveInfo != null;
}
@@ -278,7 +302,7 @@
return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null;
}
- public boolean isInstallForExistingUser() {
+ public boolean isInstallExistingForUser() {
return mInstallArgs == null;
}
@@ -380,11 +404,13 @@
return mParsedPackage;
}
- public @ParsingPackageUtils.ParseFlags int getParseFlags() {
+ @ParsingPackageUtils.ParseFlags
+ public int getParseFlags() {
return mParseFlags;
}
- public @PackageManagerService.ScanFlags int getScanFlags() {
+ @PackageManagerService.ScanFlags
+ public int getScanFlags() {
return mScanFlags;
}
@@ -406,14 +432,27 @@
return mClearCodeCache;
}
- public boolean isReplace() {
+ public boolean isInstallReplace() {
return mReplace;
}
- public boolean isSystem() {
+ public boolean isInstallSystem() {
return mSystem;
}
+ public boolean isInstallInherit() {
+ return mIsInstallInherit;
+ }
+
+ public boolean isInstallForUsers() {
+ return mIsInstallForUsers;
+ }
+
+ public boolean isInstallFromAdb() {
+ return mInstallArgs != null
+ && (mInstallArgs.mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0;
+ }
+
@Nullable
public PackageSetting getOriginalPackageSetting() {
return mOriginalPs;
@@ -520,6 +559,10 @@
return mScanResult.mRequest.mIsPlatformPackage;
}
+ public boolean isInstantInstall() {
+ return (mScanFlags & SCAN_AS_INSTANT_APP) != 0;
+ }
+
public void assertScanResultExists() {
if (mScanResult == null) {
// Should not happen. This indicates a bug in the installation code flow
@@ -529,7 +572,14 @@
Slog.e(TAG, "ScanResult is null and it should not happen");
}
}
+ }
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ public int getRequireUserAction() {
+ return mRequireUserAction;
}
public void setScanFlags(int scanFlags) {
@@ -658,4 +708,60 @@
mRemovedInfo.mRemovedAppId = appId;
}
}
+
+ public void onPrepareStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE);
+ }
+ }
+
+ public void onPrepareFinished() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_PREPARE);
+ }
+ }
+
+ public void onScanStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_SCAN);
+ }
+ }
+
+ public void onScanFinished() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_SCAN);
+ }
+ }
+
+ public void onReconcileStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_RECONCILE);
+ }
+ }
+
+ public void onReconcileFinished() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_RECONCILE);
+ }
+ }
+
+ public void onCommitStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_COMMIT);
+ }
+ }
+
+ public void onCommitFinished() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_COMMIT);
+ }
+ }
+
+ public void onInstallCompleted(Computer snapshot) {
+ if (getReturnCode() == INSTALL_SUCCEEDED) {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onInstallSucceed(snapshot);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 88469f5..d8494db 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -17,6 +17,8 @@
package com.android.server.pm;
import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -93,7 +95,11 @@
final PackageManagerService mPm;
final InstallPackageHelper mInstallPackageHelper;
final RemovePackageHelper mRemovePackageHelper;
+ final boolean mIsInherit;
+ final int mSessionId;
+ final int mRequireUserAction;
+ // For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
int installFlags, InstallSource installSource, String volumeUuid,
UserHandle user, String packageAbiOverride, int packageSource,
@@ -121,9 +127,12 @@
mRequiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
mPackageSource = packageSource;
mPackageLite = packageLite;
+ mIsInherit = false;
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
- InstallingSession(File stagedDir, IPackageInstallObserver2 observer,
+ InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
PackageLite packageLite, PackageManagerService pm) {
@@ -151,6 +160,9 @@
mRequiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
mPackageSource = sessionParams.packageSource;
mPackageLite = packageLite;
+ mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
+ mSessionId = sessionId;
+ mRequireUserAction = sessionParams.requireUserAction;
}
@Override
@@ -506,6 +518,7 @@
mInstallPackageHelper.installPackagesTraced(installRequests);
for (InstallRequest request : installRequests) {
+ request.onInstallCompleted(mPm.snapshotComputer());
doPostInstall(request);
}
}
@@ -531,7 +544,7 @@
}
private void cleanUpForFailedInstall(InstallRequest request) {
- if (request.isMoveInstall()) {
+ if (request.isInstallMove()) {
mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
request.getMovePackageName(), request.getMoveFromCodePath());
} else {
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 4e8b0a1..66ef93d 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -95,7 +95,7 @@
request.closeFreezer();
request.runPostInstallRunnable();
- if (!request.isInstallForExistingUser()) {
+ if (!request.isInstallExistingForUser()) {
mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
} else if (DEBUG_INSTALL) {
// No post-install when we run restore from installExistingPackageForUser
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 72ec510..67b948f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2550,8 +2550,8 @@
}
synchronized (mLock) {
- return new InstallingSession(stageDir, localObserver, params, mInstallSource, user,
- mSigningDetails, mInstallerUid, mPackageLite, mPm);
+ return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
+ user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
}
}
@@ -3695,7 +3695,9 @@
private boolean dispatchPendingAbandonCallback() {
final Runnable callback;
synchronized (mLock) {
- Preconditions.checkState(mStageDirInUse);
+ if (!mStageDirInUse) {
+ return false;
+ }
mStageDirInUse = false;
callback = mPendingAbandonCallback;
mPendingAbandonCallback = null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8089af3..139c6ea 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1724,8 +1724,8 @@
}
public PackageManagerService(PackageManagerServiceInjector injector, boolean factoryTest,
- final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild,
- final int sdkVersion, final String incrementalVersion) {
+ final String partitionsFingerprint, final boolean isEngBuild,
+ final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) {
mIsEngBuild = isEngBuild;
mIsUserDebugBuild = isUserDebugBuild;
mSdkVersion = sdkVersion;
@@ -1971,10 +1971,11 @@
final VersionInfo ver = mSettings.getInternalVersion();
mIsUpgrade =
- !buildFingerprint.equals(ver.fingerprint);
+ !partitionsFingerprint.equals(ver.fingerprint);
if (mIsUpgrade) {
- PackageManagerServiceUtils.logCriticalInfo(Log.INFO, "Upgrading from "
- + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT);
+ PackageManagerServiceUtils.logCriticalInfo(Log.INFO,
+ "Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
+ + PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
}
mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
@@ -2081,14 +2082,14 @@
+ ((SystemClock.uptimeMillis() - startTime) / 1000f)
+ " seconds");
- // If the build fingerprint has changed since the last time we booted,
+ // If the partitions fingerprint has changed since the last time we booted,
// we need to re-grant app permission to catch any new ones that
// appear. This is really a hack, and means that apps can in some
// cases get permissions that the user didn't initially explicitly
// allow... it would be nice to have some better way to handle
// this situation.
if (mIsUpgrade) {
- Slog.i(TAG, "Build fingerprint changed from " + ver.fingerprint + " to "
+ Slog.i(TAG, "Partitions fingerprint changed from " + ver.fingerprint + " to "
+ PackagePartitions.FINGERPRINT
+ "; regranting permissions for internal storage");
}
@@ -2120,6 +2121,7 @@
| Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES);
}
}
+ ver.buildFingerprint = Build.FINGERPRINT;
ver.fingerprint = PackagePartitions.FINGERPRINT;
}
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
new file mode 100644
index 0000000..0574f73
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -0,0 +1,201 @@
+/*
+ * 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.server.pm;
+
+import static android.os.Process.INVALID_UID;
+
+import android.annotation.IntDef;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
+
+/**
+ * Metrics class for reporting stats to logging infrastructures like Westworld
+ */
+final class PackageMetrics {
+ public static final int STEP_PREPARE = 1;
+ public static final int STEP_SCAN = 2;
+ public static final int STEP_RECONCILE = 3;
+ public static final int STEP_COMMIT = 4;
+
+ @IntDef(prefix = {"STEP_"}, value = {
+ STEP_PREPARE,
+ STEP_SCAN,
+ STEP_RECONCILE,
+ STEP_COMMIT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StepInt {
+ }
+
+ private final long mInstallStartTimestampMillis;
+ private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>();
+ private final InstallRequest mInstallRequest;
+
+ PackageMetrics(InstallRequest installRequest) {
+ // New instance is used for tracking installation metrics only.
+ // Other metrics should use static methods of this class.
+ mInstallStartTimestampMillis = System.currentTimeMillis();
+ mInstallRequest = installRequest;
+ }
+
+ public void onInstallSucceed(Computer snapshot) {
+ // TODO(b/239722919): report to SecurityLog if on work profile or managed device
+ reportInstallationStats(snapshot, true /* success */);
+ }
+
+ private void reportInstallationStats(Computer snapshot, boolean success) {
+ UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ // TODO(b/249294752): do not log if adb
+ final long installDurationMillis =
+ System.currentTimeMillis() - mInstallStartTimestampMillis;
+ // write to stats
+ final Pair<int[], long[]> stepDurations = getInstallStepDurations();
+ final int[] newUsers = mInstallRequest.getNewUsers();
+ final int[] originalUsers = mInstallRequest.getOriginUsers();
+ final String packageName = mInstallRequest.getName();
+ final String installerPackageName = mInstallRequest.getInstallerPackageName();
+ final int installerUid = installerPackageName == null ? INVALID_UID
+ : snapshot.getPackageUid(installerPackageName, 0, 0);
+ final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+ final long versionCode = success ? 0 : ps.getVersionCode();
+ final long apksSize = getApksSize(ps.getPath());
+
+ FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+ mInstallRequest.getSessionId() /* session_id */,
+ success ? null : packageName /* not report package_name on success */,
+ mInstallRequest.getUid() /* uid */,
+ newUsers /* user_ids */,
+ userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
+ originalUsers /* original_user_ids */,
+ userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */,
+ mInstallRequest.getReturnCode() /* public_return_code */,
+ 0 /* internal_error_code */,
+ apksSize /* apks_size_bytes */,
+ versionCode /* version_code */,
+ stepDurations.first /* install_steps */,
+ stepDurations.second /* step_duration_millis */,
+ installDurationMillis /* total_duration_millis */,
+ mInstallRequest.getInstallFlags() /* install_flags */,
+ installerUid /* installer_package_uid */,
+ -1 /* original_installer_package_uid */,
+ mInstallRequest.getDataLoaderType() /* data_loader_type */,
+ mInstallRequest.getRequireUserAction() /* user_action_required_type */,
+ mInstallRequest.isInstantInstall() /* is_instant */,
+ mInstallRequest.isInstallReplace() /* is_replace */,
+ mInstallRequest.isInstallSystem() /* is_system */,
+ mInstallRequest.isInstallInherit() /* is_inherit */,
+ mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */,
+ mInstallRequest.isInstallMove() /* is_move_install */,
+ false /* is_staged */
+ );
+ }
+
+ private long getApksSize(File apkDir) {
+ // TODO(b/249294752): also count apk sizes for failed installs
+ final AtomicLong apksSize = new AtomicLong();
+ try (Stream<Path> walkStream = Files.walk(apkDir.toPath())) {
+ walkStream.filter(p -> p.toFile().isFile()
+ && ApkLiteParseUtils.isApkFile(p.toFile())).forEach(
+ f -> apksSize.addAndGet(f.toFile().length()));
+ } catch (IOException e) {
+ // ignore
+ }
+ return apksSize.get();
+ }
+
+ public void onStepStarted(@StepInt int step) {
+ mInstallSteps.put(step, new InstallStep());
+ }
+
+ public void onStepFinished(@StepInt int step) {
+ final InstallStep installStep = mInstallSteps.get(step);
+ if (installStep != null) {
+ // Only valid if the start timestamp is set; otherwise no-op
+ installStep.finish();
+ }
+ }
+
+ // List of steps (e.g., 1, 2, 3) and corresponding list of durations (e.g., 200ms, 100ms, 150ms)
+ private Pair<int[], long[]> getInstallStepDurations() {
+ ArrayList<Integer> steps = new ArrayList<>();
+ ArrayList<Long> durations = new ArrayList<>();
+ for (int i = 0; i < mInstallSteps.size(); i++) {
+ final long duration = mInstallSteps.valueAt(i).getDurationMillis();
+ if (duration >= 0) {
+ steps.add(mInstallSteps.keyAt(i));
+ durations.add(mInstallSteps.valueAt(i).getDurationMillis());
+ }
+ }
+ int[] stepsArray = new int[steps.size()];
+ long[] durationsArray = new long[durations.size()];
+ for (int i = 0; i < stepsArray.length; i++) {
+ stepsArray[i] = steps.get(i);
+ durationsArray[i] = durations.get(i);
+ }
+ return new Pair<>(stepsArray, durationsArray);
+ }
+
+ private static class InstallStep {
+ private final long mStartTimestampMillis;
+ private long mDurationMillis = -1;
+
+ InstallStep() {
+ mStartTimestampMillis = System.currentTimeMillis();
+ }
+
+ void finish() {
+ mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis;
+ }
+
+ long getDurationMillis() {
+ return mDurationMillis;
+ }
+ }
+
+ public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags,
+ UserManagerInternal userManagerInternal) {
+ if (info.mIsUpdate) {
+ // Not logging uninstalls caused by app updates
+ return;
+ }
+ final int[] removedUsers = info.mRemovedUsers;
+ final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers);
+ final int[] originalUsers = info.mOrigUsers;
+ final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers);
+ FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED,
+ info.mUid, removedUsers, removedUserTypes, originalUsers, originalUserTypes,
+ deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
+ !info.mRemovedForAllUsers);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 3c863d0..4cac115 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -111,12 +111,6 @@
}
private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem) {
- // Don't send static shared library removal broadcasts as these
- // libs are visible only the apps that depend on them an one
- // cannot remove the library if it has a dependency.
- if (mIsStaticSharedLib) {
- return;
- }
Bundle extras = new Bundle();
final int removedUid = mRemovedAppId >= 0 ? mRemovedAppId : mUid;
extras.putInt(Intent.EXTRA_UID, removedUid);
@@ -128,15 +122,22 @@
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
+
+ // Send PACKAGE_REMOVED broadcast to the respective installer.
+ if (mRemovedPackage != null && mInstallerPackageName != null) {
+ mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
+ mRemovedPackage, extras, 0 /*flags*/,
+ mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
+ }
+ if (mIsStaticSharedLib) {
+ // When uninstalling static shared libraries, only the package's installer needs to be
+ // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
+ return;
+ }
if (mRemovedPackage != null) {
mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
mRemovedPackage, extras, 0, null /*targetPackage*/, null,
mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
- if (mInstallerPackageName != null) {
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
- mRemovedPackage, extras, 0 /*flags*/,
- mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
- }
mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds,
diff --git a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
index 3b306a8..b310c62a 100644
--- a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
+++ b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
@@ -16,7 +16,7 @@
package com.android.server.pm;
-import android.annotation.NonNull;;
+import android.annotation.NonNull;
import android.text.TextUtils;
import com.android.internal.util.HexDump;
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index ffce69e..99bcbc9 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -35,6 +35,7 @@
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.utils.WatchedLongSparseArray;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -48,14 +49,14 @@
* as install) led to the request.
*/
final class ReconcilePackageUtils {
- public static Map<String, ReconciledPackage> reconcilePackages(
+ public static List<ReconciledPackage> reconcilePackages(
List<InstallRequest> installRequests,
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms, Settings settings)
throws ReconcileFailure {
- final Map<String, ReconciledPackage> result = new ArrayMap<>(installRequests.size());
+ final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
// make a copy of the existing set of packages so we can combine them with incoming packages
final ArrayMap<String, AndroidPackage> combinedPackages =
@@ -67,6 +68,7 @@
new ArrayMap<>();
for (InstallRequest installRequest : installRequests) {
+ installRequest.onReconcileStarted();
final String installPackageName = installRequest.getParsedPackage().getPackageName();
// add / replace existing with incoming packages
@@ -87,10 +89,9 @@
}
-
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
- if (installRequest.isReplace() && !installRequest.isSystem()) {
+ if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
final int deleteFlags = PackageManager.DELETE_KEEP_DATA
| (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
@@ -256,13 +257,11 @@
}
}
- result.put(installPackageName,
+ final ReconciledPackage reconciledPackage =
new ReconciledPackage(installRequests, allPackages, installRequest,
deletePackageAction, allowedSharedLibInfos, signingDetails,
- sharedUserSignaturesChanged, removeAppKeySetData));
- }
+ sharedUserSignaturesChanged, removeAppKeySetData);
- for (InstallRequest installRequest : installRequests) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
@@ -270,20 +269,21 @@
// library paths after the scan is done. Also during the initial
// scan don't update any libs as we do this wholesale after all
// apps are scanned to avoid dependency based scanning.
- if ((installRequest.getScanFlags() & SCAN_BOOTING) != 0
- || (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
- != 0) {
- continue;
+ if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0
+ && (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+ == 0) {
+ try {
+ reconciledPackage.mCollectedSharedLibraryInfos =
+ sharedLibraries.collectSharedLibraryInfos(
+ installRequest.getParsedPackage(), combinedPackages,
+ incomingSharedLibraries);
+ } catch (PackageManagerException e) {
+ throw new ReconcileFailure(e.error, e.getMessage());
+ }
}
- final String installPackageName = installRequest.getParsedPackage().getPackageName();
- try {
- result.get(installPackageName).mCollectedSharedLibraryInfos =
- sharedLibraries.collectSharedLibraryInfos(
- installRequest.getParsedPackage(), combinedPackages,
- incomingSharedLibraries);
- } catch (PackageManagerException e) {
- throw new ReconcileFailure(e.error, e.getMessage());
- }
+
+ installRequest.onReconcileFinished();
+ result.add(reconciledPackage);
}
return result;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 45c0d6e..e33cc9f 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -353,6 +353,7 @@
private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint";
private static final String ATTR_FINGERPRINT = "fingerprint";
private static final String ATTR_VOLUME_UUID = "volumeUuid";
private static final String ATTR_SDK_VERSION = "sdkVersion";
@@ -432,7 +433,12 @@
int databaseVersion;
/**
- * Last known value of {@link Build#FINGERPRINT}. Used to determine when
+ * Last known value of {@link Build#FINGERPRINT}. Stored for debug purposes.
+ */
+ String buildFingerprint;
+
+ /**
+ * Last known value of {@link PackagePartitions#FINGERPRINT}. Used to determine when
* an system update has occurred, meaning we need to clear code caches.
*/
String fingerprint;
@@ -444,6 +450,7 @@
public void forceCurrent() {
sdkVersion = Build.VERSION.SDK_INT;
databaseVersion = CURRENT_DATABASE_VERSION;
+ buildFingerprint = Build.FINGERPRINT;
fingerprint = PackagePartitions.FINGERPRINT;
}
}
@@ -2495,6 +2502,8 @@
XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid);
serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion);
serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion);
+ XmlUtils.writeStringAttribute(serializer, ATTR_BUILD_FINGERPRINT,
+ ver.buildFingerprint);
XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint);
serializer.endTag(null, TAG_VERSION);
}
@@ -3105,6 +3114,8 @@
internal.sdkVersion = parser.getAttributeInt(null, "internal", 0);
external.sdkVersion = parser.getAttributeInt(null, "external", 0);
+ internal.buildFingerprint = external.buildFingerprint =
+ XmlUtils.readStringAttribute(parser, "buildFingerprint");
internal.fingerprint = external.fingerprint =
XmlUtils.readStringAttribute(parser, "fingerprint");
@@ -3136,6 +3147,8 @@
final VersionInfo ver = findOrCreateVersion(volumeUuid);
ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION);
ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
+ ver.buildFingerprint = XmlUtils.readStringAttribute(parser,
+ ATTR_BUILD_FINGERPRINT);
ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
} else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
mDomainVerificationManager.readSettings(computer, parser);
@@ -4514,6 +4527,7 @@
pw.printPair("sdkVersion", ver.sdkVersion);
pw.printPair("databaseVersion", ver.databaseVersion);
pw.println();
+ pw.printPair("buildFingerprint", ver.buildFingerprint);
pw.printPair("fingerprint", ver.fingerprint);
pw.println();
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index fbfc84a..4f7c2bd 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -208,7 +208,7 @@
synchronized (mPm.mLock) {
final boolean isUpgrade = !PackagePartitions.FINGERPRINT.equals(ver.fingerprint);
if (isUpgrade) {
- logCriticalInfo(Log.INFO, "Build fingerprint changed from " + ver.fingerprint
+ logCriticalInfo(Log.INFO, "Partitions fingerprint changed from " + ver.fingerprint
+ " to " + PackagePartitions.FINGERPRINT + "; regranting permissions for "
+ volumeUuid);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 56ec8e4..1027f4c 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -25,6 +25,7 @@
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.UserManager;
+import android.util.DebugUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -46,6 +47,18 @@
public @interface OwnerType {
}
+ public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
+ public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+ public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
+
+ private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT_";
+ @IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
+ USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+ USER_ASSIGNMENT_RESULT_FAILURE
+ })
+ public @interface UserAssignmentResult {}
+
public interface UserRestrictionsListener {
/**
* Called when a user restriction changes.
@@ -77,6 +90,23 @@
}
/**
+ * Listener for {@link UserManager#isUserVisible() user visibility} changes.
+ */
+ public interface UserVisibilityListener {
+
+ /**
+ * Called when the {@link UserManager#isUserVisible() user visibility} changed.
+ *
+ * <p><b>Note:</b> this method is called independently of
+ * {@link com.android.server.SystemService} callbacks; for example, the call with
+ * {@code visible} {@code true} might be called before the
+ * {@link com.android.server.SystemService#onUserStarting(com.android.server.SystemService.TargetUser)}
+ * call.
+ */
+ void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+ }
+
+ /**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
* restrictions enforced by the user.
*
@@ -326,29 +356,28 @@
public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
/**
- * Assigns a user to a display.
- *
- * <p>On most devices this call will be a no-op, but it will be used on devices that support
- * multiple users on multiple displays (like automotives with passenger displays).
- *
- * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
- * check it. In fact, one of the intended clients for this method is
- * {@code DisplayManagerService}, which will call it when a virtual display is created (another
- * client is {@code UserController}, which will call it when a user is started).
- *
- */
- public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
-
- /**
- * Unassigns a user from its current display.
- *
- * <p>On most devices this call will be a no-op, but it will be used on devices that support
- * multiple users on multiple displays (like automotives with passenger displays).
+ * Assigns a user to a display when it's starting, returning whether the assignment succeeded
+ * and the user is {@link UserManager#isUserVisible() visible}.
*
* <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
- * is stopped) and {@code DisplayManagerService} (when a virtual display is destroyed).
+ * is started). If other clients (like {@code CarService} need to explicitly change the user /
+ * display assignment, we'll need to provide other APIs.
+ *
+ * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
+ * pass a valid display id.
*/
- public abstract void unassignUserFromDisplay(@UserIdInt int userId);
+ public abstract @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
+ @UserIdInt int profileGroupId,
+ boolean foreground, int displayId);
+
+ /**
+ * Unassigns a user from its current display when it's stopping.
+ *
+ * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
+ * is stopped). If other clients (like {@code CarService} need to explicitly change the user /
+ * display assignment, we'll need to provide other APIs.
+ */
+ public abstract void unassignUserFromDisplayOnStop(@UserIdInt int userId);
/**
* Returns {@code true} if the user is visible (as defined by
@@ -390,4 +419,26 @@
* would make such call).
*/
public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
+
+ /**
+ * Gets the user-friendly representation of the {@code result} of a
+ * {@link #assignUserToDisplayOnStart(int, int, boolean, int)} call.
+ */
+ public static String userAssignmentResultToString(@UserAssignmentResult int result) {
+ return DebugUtils.constantToString(UserManagerInternal.class, PREFIX_USER_ASSIGNMENT_RESULT,
+ result);
+ }
+
+ /** Adds a {@link UserVisibilityListener}. */
+ public abstract void addUserVisibilityListener(UserVisibilityListener listener);
+
+ /** Removes a {@link UserVisibilityListener}. */
+ public abstract void removeUserVisibilityListener(UserVisibilityListener listener);
+
+ /** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
+ public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+
+ /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
+ */
+ public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f85b11d..88e12fa 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
@@ -96,6 +97,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Slog;
@@ -124,9 +126,11 @@
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemService;
+import com.android.server.am.EventLogTags;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
+import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -261,7 +265,7 @@
@VisibleForTesting
static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
- private static final int USER_VERSION = 10;
+ private static final int USER_VERSION = 11;
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
@@ -504,6 +508,10 @@
@GuardedBy("mUserLifecycleListeners")
private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
+ // TODO(b/244333150): temporary array, should belong to UserVisibilityMediator
+ @GuardedBy("mUserVisibilityListeners")
+ private final ArrayList<UserVisibilityListener> mUserVisibilityListeners = new ArrayList<>();
+
private final LockPatternUtils mLockPatternUtils;
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -725,6 +733,7 @@
mPackagesLock = packagesLock;
mUsers = users != null ? users : new SparseArray<>();
mHandler = new MainHandler();
+ mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
mUserDataPreparer = userDataPreparer;
mUserTypes = UserTypeFactory.getUserTypes();
invalidateOwnerNameIfNecessary(context.getResources(), true /* forceUpdate */);
@@ -749,7 +758,6 @@
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
emulateSystemUserModeIfNeeded();
- mUserVisibilityMediator = new UserVisibilityMediator(this);
}
void systemReady() {
@@ -1638,8 +1646,7 @@
return isProfileUnchecked(userId);
}
- // TODO(b/244644281): make it private once UserVisibilityMediator don't use it anymore
- boolean isProfileUnchecked(@UserIdInt int userId) {
+ private boolean isProfileUnchecked(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isProfile();
@@ -1776,26 +1783,14 @@
}
@Override
- public List<UserHandle> getVisibleUsers() {
+ public int[] getVisibleUsers() {
if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
throw new SecurityException("Caller needs MANAGE_USERS or INTERACT_ACROSS_USERS "
+ "permission to get list of visible users");
}
final long ident = Binder.clearCallingIdentity();
try {
- // TODO(b/2399825580): refactor into UserDisplayAssigner
- synchronized (mUsersLock) {
- int usersSize = mUsers.size();
- ArrayList<UserHandle> visibleUsers = new ArrayList<>(usersSize);
- for (int i = 0; i < usersSize; i++) {
- UserInfo ui = mUsers.valueAt(i).info;
- if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
- && mUserVisibilityMediator.isUserVisible(ui.id)) {
- visibleUsers.add(UserHandle.of(ui.id));
- }
- }
- return visibleUsers;
- }
+ return mUserVisibilityMediator.getVisibleUsers().toArray();
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2807,7 +2802,8 @@
synchronized (mUsersLock) {
count = getAliveUsersExcludingGuestsCountLU();
}
- return count >= UserManager.getMaxSupportedUsers();
+ return count >= UserManager.getMaxSupportedUsers()
+ && !isCreationOverrideEnabled();
}
/**
@@ -2817,15 +2813,16 @@
* <p>For checking whether more profiles can be added to a particular parent use
* {@link #canAddMoreProfilesToUser}.
*/
- private boolean canAddMoreUsersOfType(UserTypeDetails userTypeDetails) {
- if (!userTypeDetails.isEnabled()) {
+ private boolean canAddMoreUsersOfType(@NonNull UserTypeDetails userTypeDetails) {
+ if (!isUserTypeEnabled(userTypeDetails)) {
return false;
}
final int max = userTypeDetails.getMaxAllowed();
if (max == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
return true; // Indicates that there is no max.
}
- return getNumberOfUsersOfType(userTypeDetails.getName()) < max;
+ return getNumberOfUsersOfType(userTypeDetails.getName()) < max
+ || isCreationOverrideEnabled();
}
/**
@@ -2836,7 +2833,7 @@
public int getRemainingCreatableUserCount(String userType) {
checkQueryOrCreateUsersPermission("get the remaining number of users that can be added.");
final UserTypeDetails type = mUserTypes.get(userType);
- if (type == null || !type.isEnabled()) {
+ if (type == null || !isUserTypeEnabled(type)) {
return 0;
}
synchronized (mUsersLock) {
@@ -2910,7 +2907,21 @@
public boolean isUserTypeEnabled(String userType) {
checkCreateUsersPermission("check if user type is enabled.");
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
- return userTypeDetails != null && userTypeDetails.isEnabled();
+ return userTypeDetails != null && isUserTypeEnabled(userTypeDetails);
+ }
+
+ /** Returns whether the creation of users of the given user type is enabled on this device. */
+ private boolean isUserTypeEnabled(@NonNull UserTypeDetails userTypeDetails) {
+ return userTypeDetails.isEnabled() || isCreationOverrideEnabled();
+ }
+
+ /**
+ * Returns whether to almost-always allow creating users even beyond their limit or if disabled.
+ * For Debug builds only.
+ */
+ private boolean isCreationOverrideEnabled() {
+ return Build.isDebuggable()
+ && SystemProperties.getBoolean(DEV_CREATE_OVERRIDE_PROPERTY, false);
}
@Override
@@ -2923,7 +2934,8 @@
@Override
public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId,
boolean allowedToRemoveOne) {
- return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne);
+ return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne)
+ || isCreationOverrideEnabled();
}
@Override
@@ -2941,7 +2953,7 @@
checkQueryOrCreateUsersPermission(
"get the remaining number of profiles that can be added to the given user.");
final UserTypeDetails type = mUserTypes.get(userType);
- if (type == null || !type.isEnabled()) {
+ if (type == null || !isUserTypeEnabled(type)) {
return 0;
}
// Managed profiles have their own specific rules.
@@ -3330,6 +3342,7 @@
final int oldFlags = systemUserData.info.flags;
final int newFlags;
final String newUserType;
+ // TODO(b/256624031): Also handle FLAG_MAIN
if (newHeadlessSystemUserMode) {
newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
newFlags = oldFlags & ~UserInfo.FLAG_FULL;
@@ -3622,6 +3635,22 @@
userVersion = 10;
}
+ if (userVersion < 11) {
+ // Add FLAG_MAIN
+ if (isHeadlessSystemUserMode()) {
+ final UserInfo earliestCreatedUser = getEarliestCreatedFullUser();
+ earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
+ userIdsToWrite.add(earliestCreatedUser.id);
+ } else {
+ synchronized (mUsersLock) {
+ final UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
+ userData.info.flags |= UserInfo.FLAG_MAIN;
+ userIdsToWrite.add(userData.info.id);
+ }
+ }
+ userVersion = 11;
+ }
+
// Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
// Done with userVersion changes, moving on to deal with userTypeVersion upgrades
@@ -3751,6 +3780,21 @@
userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType);
}
+ private UserInfo getEarliestCreatedFullUser() {
+ final List<UserInfo> users = getUsersInternal(true, true, true);
+ UserInfo earliestUser = users.get(0);
+ long earliestCreationTime = earliestUser.creationTime;
+ for (int i = 0; i < users.size(); i++) {
+ final UserInfo info = users.get(i);
+ if (info.isFull() && info.isAdmin() && info.creationTime > 0
+ && info.creationTime < earliestCreationTime) {
+ earliestCreationTime = info.creationTime;
+ earliestUser = info;
+ }
+ }
+ return earliestUser;
+ }
+
@GuardedBy({"mPackagesLock"})
private void fallbackToSingleUserLP() {
int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
@@ -4400,7 +4444,7 @@
+ ") indicated SYSTEM user, which cannot be created.");
return null;
}
- if (!userTypeDetails.isEnabled()) {
+ if (!isUserTypeEnabled(userTypeDetails)) {
throwCheckedUserOperationException(
"Cannot add a user of disabled type " + userType + ".",
UserManager.USER_OPERATION_ERROR_MAX_USERS);
@@ -4472,27 +4516,12 @@
+ " for user " + parentId,
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
- // In legacy mode, restricted profile's parent can only be the owner user
- if (isRestricted && !UserManager.isSplitSystemUser()
- && (parentId != UserHandle.USER_SYSTEM)) {
+ if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
+ && !isCreationOverrideEnabled()) {
throwCheckedUserOperationException(
- "Cannot add restricted profile - parent user must be owner",
+ "Cannot add restricted profile - parent user must be system",
UserManager.USER_OPERATION_ERROR_UNKNOWN);
}
- if (isRestricted && UserManager.isSplitSystemUser()) {
- if (parent == null) {
- throwCheckedUserOperationException(
- "Cannot add restricted profile - parent user must be specified",
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
- }
- if (!parent.info.canHaveProfile()) {
- throwCheckedUserOperationException(
- "Cannot add restricted profile - profiles cannot be created for "
- + "the specified parent user id "
- + parentId,
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
- }
- }
userId = getNextAvailableId();
Slog.i(LOG_TAG, "Creating user " + userId + " of type " + userType);
@@ -5248,7 +5277,8 @@
Slog.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
}
- if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+ if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+ && userData.info.isProfile()) {
sendProfileRemovedBroadcast(userData.info.profileGroupId, userData.info.id,
userData.info.userType);
}
@@ -6146,7 +6176,7 @@
dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
return;
case "--visibility-mediator":
- mUserVisibilityMediator.dump(pw);
+ mUserVisibilityMediator.dump(pw, args);
return;
}
}
@@ -6212,7 +6242,7 @@
} // synchronized (mPackagesLock)
pw.println();
- mUserVisibilityMediator.dump(pw);
+ mUserVisibilityMediator.dump(pw, args);
pw.println();
// Dump some capabilities
@@ -6249,6 +6279,9 @@
synchronized (mUserLifecycleListeners) {
pw.println(" user lifecycle events: " + mUserLifecycleListeners.size());
}
+ synchronized (mUserVisibilityListeners) {
+ pw.println(" user visibility events: " + mUserVisibilityListeners.size());
+ }
// Dump UserTypes
pw.println();
@@ -6788,13 +6821,15 @@
}
@Override
- public void assignUserToDisplay(@UserIdInt int userId, int displayId) {
- mUserVisibilityMediator.assignUserToDisplay(userId, displayId);
+ public int assignUserToDisplayOnStart(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ boolean foreground, int displayId) {
+ return mUserVisibilityMediator.assignUserToDisplayOnStart(userId, profileGroupId,
+ foreground, displayId);
}
@Override
- public void unassignUserFromDisplay(@UserIdInt int userId) {
- mUserVisibilityMediator.unassignUserFromDisplay(userId);
+ public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
+ mUserVisibilityMediator.unassignUserFromDisplayOnStop(userId);
}
@Override
@@ -6816,8 +6851,57 @@
public @UserIdInt int getUserAssignedToDisplay(int displayId) {
return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
}
+
+ @Override
+ public void addUserVisibilityListener(UserVisibilityListener listener) {
+ synchronized (mUserVisibilityListeners) {
+ mUserVisibilityListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void removeUserVisibilityListener(UserVisibilityListener listener) {
+ synchronized (mUserVisibilityListeners) {
+ mUserVisibilityListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+ mHandler.post(() -> {
+ UserVisibilityListener[] listeners;
+ synchronized (mUserVisibilityListeners) {
+ listeners = new UserVisibilityListener[mUserVisibilityListeners.size()];
+ mUserVisibilityListeners.toArray(listeners);
+ }
+ for (UserVisibilityListener listener : listeners) {
+ listener.onUserVisibilityChanged(userId, visible);
+ }
+ });
+ }
+
+ @Override
+ public int[] getUserTypesForStatsd(@UserIdInt int[] userIds) {
+ if (userIds == null) {
+ return null;
+ }
+ final int[] userTypes = new int[userIds.length];
+ for (int i = 0; i < userTypes.length; i++) {
+ final UserInfo userInfo = getUserInfo(userIds[i]);
+ if (userInfo == null) {
+ // Not possible because the input user ids should all be valid
+ userTypes[i] = UserManager.getUserTypeForStatsd("");
+ } else {
+ userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+ }
+ }
+ return userTypes;
+ }
} // class LocalService
+
+
/**
* Check if user has restrictions
* @param restriction restrictions to check
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index dbd026e..27d74d5 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -148,7 +148,8 @@
UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
- UserManager.DISALLOW_CELLULAR_2G
+ UserManager.DISALLOW_CELLULAR_2G,
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -197,7 +198,8 @@
UserManager.DISALLOW_WIFI_TETHERING,
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
- UserManager.DISALLOW_CELLULAR_2G
+ UserManager.DISALLOW_CELLULAR_2G,
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
);
/**
@@ -237,7 +239,8 @@
UserManager.DISALLOW_WIFI_TETHERING,
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
- UserManager.DISALLOW_CELLULAR_2G
+ UserManager.DISALLOW_CELLULAR_2G,
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
);
/**
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 5b6196f..8fb5773 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -126,10 +126,13 @@
.setCrossProfileIntentFilterAccessControl(
CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
.setIsCredentialSharableWithParent(true)
+ .setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
- .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT));
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT)
+ .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+ .setUseParentsContacts(true));
}
/**
@@ -259,7 +262,8 @@
private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_FULL_SYSTEM)
- .setBaseType(FLAG_SYSTEM | FLAG_FULL);
+ .setBaseType(FLAG_SYSTEM | FLAG_FULL)
+ .setDefaultUserInfoPropertyFlags(UserInfo.FLAG_MAIN);
}
/**
@@ -309,6 +313,10 @@
return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters();
}
+ private static List<DefaultCrossProfileIntentFilter> getDefaultCloneCrossProfileIntentFilter() {
+ return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
+ }
+
/**
* Reads the given xml parser to obtain device user-type customization, and updates the given
* map of {@link UserTypeDetails.Builder}s accordingly.
@@ -390,6 +398,7 @@
}
setIntAttribute(parser, "enabled", builder::setEnabled);
+ setIntAttribute(parser, "max-allowed", builder::setMaxAllowed);
// Process child elements.
final int depth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index f725c48..9c4187b 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -15,204 +15,401 @@
*/
package com.android.server.pm;
+import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Dumpable;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.SparseIntArray;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
+import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Class responsible for deciding whether a user is visible (or visible for a given display).
*
+ * <p>Currently, it has 2 "modes" (set on constructor), which defines the class behavior (i.e, the
+ * logic that dictates the result of methods such as {@link #isUserVisible(int)} and
+ * {@link #isUserVisible(int, int)}):
+ *
+ * <ul>
+ * <li>default: this is the most common mode (used by phones, tablets, foldables, automotives with
+ * just cluster and driver displayes, etc...), where the logic is based solely on the current
+ * foreground user (and its started profiles)
+ * <li>{@code MUMD}: mode for "(concurrent) Multiple Users on Multiple Displays", which is used on
+ * automotives with passenger display. In this mode, users started in background on the secondary
+ * display are stored in map.
+ * </ul>
+ *
* <p>This class is thread safe.
*/
-// TODO(b/244644281): improve javadoc (for example, explain all cases / modes)
-public final class UserVisibilityMediator {
+public final class UserVisibilityMediator implements Dumpable {
private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG = UserVisibilityMediator.class.getSimpleName();
- private final Object mLock = new Object();
+ public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1;
+ public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2;
+ public static final int SECONDARY_DISPLAY_MAPPING_FAILED = -1;
- // TODO(b/244644281): should not depend on service, but keep its own internal state (like
- // current user and profile groups), but it is initially as the code was just moved from UMS
- // "as is". Similarly, it shouldn't need to pass the SparseIntArray on constructor (which was
- // added to UMS for testing purposes)
- private final UserManagerService mService;
+ /**
+ * Whether a user / display assignment requires adding an entry to the
+ * {@code mUsersOnSecondaryDisplays} map.
+ */
+ @IntDef(flag = false, prefix = {"SECONDARY_DISPLAY_MAPPING_"}, value = {
+ SECONDARY_DISPLAY_MAPPING_NEEDED,
+ SECONDARY_DISPLAY_MAPPING_NOT_NEEDED,
+ SECONDARY_DISPLAY_MAPPING_FAILED
+ })
+ public @interface SecondaryDisplayMappingStatus {}
+
+ // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+ @VisibleForTesting
+ static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
+
+ private final Object mLock = new Object();
private final boolean mUsersOnSecondaryDisplaysEnabled;
+ @UserIdInt
+ @GuardedBy("mLock")
+ private int mCurrentUserId = INITIAL_CURRENT_USER_ID;
+
+ /**
+ * Map of background users started on secondary displays.
+ *
+ * <p>Only set when {@code mUsersOnSecondaryDisplaysEnabled} is {@code true}.
+ */
@Nullable
@GuardedBy("mLock")
private final SparseIntArray mUsersOnSecondaryDisplays;
- UserVisibilityMediator(UserManagerService service) {
- this(service, UserManager.isUsersOnSecondaryDisplaysEnabled(),
- /* usersOnSecondaryDisplays= */ null);
+ /**
+ * Mapping from each started user to its profile group.
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
+
+ /**
+ * Handler user to call listeners
+ */
+ private final Handler mHandler;
+
+ // @GuardedBy("mLock") - hold lock for writes, no lock necessary for simple reads
+ final CopyOnWriteArrayList<UserVisibilityListener> mListeners =
+ new CopyOnWriteArrayList<>();
+
+ UserVisibilityMediator(Handler handler) {
+ this(UserManager.isUsersOnSecondaryDisplaysEnabled(), handler);
}
@VisibleForTesting
- UserVisibilityMediator(UserManagerService service, boolean usersOnSecondaryDisplaysEnabled,
- @Nullable SparseIntArray usersOnSecondaryDisplays) {
- mService = service;
+ UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled, Handler handler) {
mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
- if (mUsersOnSecondaryDisplaysEnabled) {
- mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
- ? new SparseIntArray() // default behavior
- : usersOnSecondaryDisplays; // passed by unit test
- } else {
- mUsersOnSecondaryDisplays = null;
- }
+ mUsersOnSecondaryDisplays = mUsersOnSecondaryDisplaysEnabled ? new SparseIntArray() : null;
+ mHandler = handler;
+ // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+ mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
}
/**
- * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
+ * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}.
*/
- public void assignUserToDisplay(int userId, int displayId) {
+ public @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
+ @UserIdInt int unResolvedProfileGroupId, boolean foreground, int displayId) {
+ Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
+ userId);
+ // This method needs to perform 4 actions:
+ //
+ // 1. Check if the user can be started given the provided arguments
+ // 2. If it can, decide whether it's visible or not (which is the return value)
+ // 3. Update the current user / profiles state
+ // 4. Update the users on secondary display state (if applicable)
+ //
+ // Notice that steps 3 and 4 should be done atomically (i.e., while holding mLock), so the
+ // previous steps are delegated to other methods (canAssignUserToDisplayLocked() and
+ // getUserVisibilityOnStartLocked() respectively).
+
+
+ int profileGroupId = unResolvedProfileGroupId == NO_PROFILE_GROUP_ID
+ ? userId
+ : unResolvedProfileGroupId;
if (DBG) {
- Slogf.d(TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
+ Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %b, %d): actualProfileGroupId=%d",
+ userId, unResolvedProfileGroupId, foreground, displayId, profileGroupId);
}
- // NOTE: Using Boolean instead of boolean as it will be re-used below
- Boolean isProfile = null;
- if (displayId == Display.DEFAULT_DISPLAY) {
- if (mUsersOnSecondaryDisplaysEnabled) {
- // Profiles are only supported in the default display, but it cannot return yet
- // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
- // (this is done indirectly below when it checks that the profile parent is the
- // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
- isProfile = isProfileUnchecked(userId);
- }
- if (isProfile == null || !isProfile) {
- // Don't need to do anything because methods (such as isUserVisible()) already
- // know that the current user (and their profiles) is assigned to the default
- // display.
- if (DBG) {
- Slogf.d(TAG, "ignoring on default display");
- }
- return;
- }
- }
-
- if (!mUsersOnSecondaryDisplaysEnabled) {
- throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", "
- + displayId + ") called on device that doesn't support multiple "
- + "users on multiple displays");
- }
-
- Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system "
- + "user to secondary display (%d)", displayId);
- Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY,
- "Cannot assign to INVALID_DISPLAY (%d)", displayId);
-
- int currentUserId = getCurrentUserId();
- Preconditions.checkArgument(userId != currentUserId,
- "Cannot assign current user (%d) to other displays", currentUserId);
-
- if (isProfile == null) {
- isProfile = isProfileUnchecked(userId);
- }
+ int result;
+ IntArray visibleUsersBefore, visibleUsersAfter;
synchronized (mLock) {
- if (isProfile) {
- // Profile can only start in the same display as parent. And for simplicity,
- // that display must be the DEFAULT_DISPLAY.
- Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
- "Profile user can only be started in the default display");
- int parentUserId = getProfileParentId(userId);
- Preconditions.checkArgument(parentUserId == currentUserId,
- "Only profile of current user can be assigned to a display");
- if (DBG) {
- Slogf.d(TAG, "Ignoring profile user %d on default display", userId);
- }
- return;
- }
-
- // Check if display is available
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
- int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
- if (DBG) {
- Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
- i, assignedUserId, assignedDisplayId);
- }
- if (displayId == assignedDisplayId) {
- throw new IllegalStateException("Cannot assign user " + userId + " to "
- + "display " + displayId + " because such display is already "
- + "assigned to user " + assignedUserId);
- }
- if (userId == assignedUserId) {
- throw new IllegalStateException("Cannot assign user " + userId + " to "
- + "display " + displayId + " because such user is as already "
- + "assigned to display " + assignedDisplayId);
- }
- }
-
+ result = getUserVisibilityOnStartLocked(userId, profileGroupId, foreground, displayId);
if (DBG) {
- Slogf.d(TAG, "Adding full user %d -> display %d", userId, displayId);
+ Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)",
+ userAssignmentResultToString(result));
}
- mUsersOnSecondaryDisplays.put(userId, displayId);
+ if (result == USER_ASSIGNMENT_RESULT_FAILURE) {
+ return result;
+ }
+
+ int mappingResult = canAssignUserToDisplayLocked(userId, profileGroupId, displayId);
+ if (mappingResult == SECONDARY_DISPLAY_MAPPING_FAILED) {
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
+
+ visibleUsersBefore = getVisibleUsers();
+
+ // Set current user / profiles state
+ if (foreground) {
+ mCurrentUserId = userId;
+ }
+ if (DBG) {
+ Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId);
+ }
+ mStartedProfileGroupIds.put(userId, profileGroupId);
+
+ // Set user / display state
+ switch (mappingResult) {
+ case SECONDARY_DISPLAY_MAPPING_NEEDED:
+ if (DBG) {
+ Slogf.d(TAG, "adding user / display mapping (%d -> %d)", userId, displayId);
+ }
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ break;
+ case SECONDARY_DISPLAY_MAPPING_NOT_NEEDED:
+ if (DBG) {
+ // Don't need to do set state because methods (such as isUserVisible())
+ // already know that the current user (and their profiles) is assigned to
+ // the default display.
+ Slogf.d(TAG, "don't need to update mUsersOnSecondaryDisplays");
+ }
+ break;
+ default:
+ Slogf.wtf(TAG, "invalid resut from canAssignUserToDisplayLocked: %d",
+ mappingResult);
+ }
+
+ visibleUsersAfter = getVisibleUsers();
}
+
+ dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter);
+
+ if (DBG) {
+ Slogf.d(TAG, "returning %s", userAssignmentResultToString(result));
+ }
+
+ return result;
+ }
+
+ @GuardedBy("mLock")
+ @UserAssignmentResult
+ private int getUserVisibilityOnStartLocked(@UserIdInt int userId,
+ @UserIdInt int profileGroupId, boolean foreground, int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ if (foreground) {
+ Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start "
+ + "foreground user on secondary display", userId, profileGroupId,
+ foreground, displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on "
+ + "device that doesn't support multiple users on multiple displays",
+ userId, profileGroupId, foreground, displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
+ }
+
+ if (isProfile(userId, profileGroupId)) {
+ if (displayId != DEFAULT_DISPLAY) {
+ Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user "
+ + "on secondary display", userId, profileGroupId, foreground,
+ displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
+ if (foreground) {
+ Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
+ + "foreground", userId, profileGroupId, foreground, displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ } else {
+ boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
+ if (DBG) {
+ Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay);
+ }
+ return isParentVisibleOnDisplay
+ ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+ : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+ }
+ }
+
+ return foreground || displayId != DEFAULT_DISPLAY
+ ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+ : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+ }
+
+ @GuardedBy("mLock")
+ @SecondaryDisplayMappingStatus
+ private int canAssignUserToDisplayLocked(@UserIdInt int userId,
+ @UserIdInt int profileGroupId, int displayId) {
+ if (displayId == DEFAULT_DISPLAY
+ && (!mUsersOnSecondaryDisplaysEnabled || !isProfile(userId, profileGroupId))) {
+ // Don't need to do anything because methods (such as isUserVisible()) already
+ // know that the current user (and its profiles) is assigned to the default display.
+ // But on MUMD devices, profiles are only supported in the default display, so it
+ // cannot return yet as it needs to check if the parent is also assigned to the
+ // DEFAULT_DISPLAY (this is done indirectly below when it checks that the profile parent
+ // is the current user, as the current user is always assigned to the DEFAULT_DISPLAY).
+ if (DBG) {
+ Slogf.d(TAG, "ignoring mapping for default display");
+ }
+ return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
+ }
+
+ if (userId == UserHandle.USER_SYSTEM) {
+ Slogf.w(TAG, "Cannot assign system user to secondary display (%d)", displayId);
+ return SECONDARY_DISPLAY_MAPPING_FAILED;
+ }
+ if (displayId == Display.INVALID_DISPLAY) {
+ Slogf.w(TAG, "Cannot assign to INVALID_DISPLAY (%d)", displayId);
+ return SECONDARY_DISPLAY_MAPPING_FAILED;
+ }
+ if (userId == mCurrentUserId) {
+ Slogf.w(TAG, "Cannot assign current user (%d) to other displays", userId);
+ return SECONDARY_DISPLAY_MAPPING_FAILED;
+ }
+
+ if (isProfile(userId, profileGroupId)) {
+ // Profile can only start in the same display as parent. And for simplicity,
+ // that display must be the DEFAULT_DISPLAY.
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ Slogf.w(TAG, "Profile user can only be started in the default display");
+ return SECONDARY_DISPLAY_MAPPING_FAILED;
+
+ }
+ if (DBG) {
+ Slogf.d(TAG, "Don't need to map profile user %d to default display", userId);
+ }
+ return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
+ }
+
+ // Check if display is available
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
+ int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
+ if (DBG) {
+ Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
+ i, assignedUserId, assignedDisplayId);
+ }
+ if (displayId == assignedDisplayId) {
+ Slogf.w(TAG, "Cannot assign user %d to display %d because such display is already "
+ + "assigned to user %d", userId, displayId, assignedUserId);
+ return SECONDARY_DISPLAY_MAPPING_FAILED;
+ }
+ if (userId == assignedUserId) {
+ Slogf.w(TAG, "Cannot assign user %d to display %d because such user is as already "
+ + "assigned to display %d", userId, displayId, assignedUserId);
+ return SECONDARY_DISPLAY_MAPPING_FAILED;
+ }
+ }
+ return SECONDARY_DISPLAY_MAPPING_NEEDED;
}
/**
- * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+ * See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}.
*/
- public void unassignUserFromDisplay(int userId) {
+ public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
if (DBG) {
- Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+ Slogf.d(TAG, "unassignUserFromDisplayOnStop(%d)", userId);
}
+ IntArray visibleUsersBefore, visibleUsersAfter;
+ synchronized (mLock) {
+ visibleUsersBefore = getVisibleUsers();
+
+ unassignUserFromDisplayOnStopLocked(userId);
+
+ visibleUsersAfter = getVisibleUsers();
+ }
+ dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter);
+ }
+
+ @GuardedBy("mLock")
+ private void unassignUserFromDisplayOnStopLocked(@UserIdInt int userId) {
+ if (DBG) {
+ Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
+ mStartedProfileGroupIds);
+ }
+ mStartedProfileGroupIds.delete(userId);
+
if (!mUsersOnSecondaryDisplaysEnabled) {
- // Don't need to do anything because methods (such as isUserVisible()) already know
- // that the current user (and their profiles) is assigned to the default display.
- if (DBG) {
- Slogf.d(TAG, "ignoring when device doesn't support MUMD");
- }
+ // Don't need to do update mUsersOnSecondaryDisplays because methods (such as
+ // isUserVisible()) already know that the current user (and their profiles) is
+ // assigned to the default display.
return;
}
-
- synchronized (mLock) {
- if (DBG) {
- Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
- mUsersOnSecondaryDisplays);
- }
- mUsersOnSecondaryDisplays.delete(userId);
+ if (DBG) {
+ Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
+ mUsersOnSecondaryDisplays);
}
+ mUsersOnSecondaryDisplays.delete(userId);
}
/**
* See {@link UserManagerInternal#isUserVisible(int)}.
*/
- public boolean isUserVisible(int userId) {
+ public boolean isUserVisible(@UserIdInt int userId) {
// First check current foreground user and their profiles (on main display)
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ if (DBG) {
+ Slogf.d(TAG, "isUserVisible(%d): true to current user or profile", userId);
+ }
return true;
}
// Device doesn't support multiple users on multiple displays, so only users checked above
// can be visible
if (!mUsersOnSecondaryDisplaysEnabled) {
+ if (DBG) {
+ Slogf.d(TAG, "isUserVisible(%d): false for non-current user on MUMD", userId);
+ }
return false;
}
+ boolean visible;
synchronized (mLock) {
- return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
+ visible = mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
}
+ if (DBG) {
+ Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible);
+ }
+ return visible;
}
/**
* See {@link UserManagerInternal#isUserVisible(int, int)}.
*/
- public boolean isUserVisible(int userId, int displayId) {
+ public boolean isUserVisible(@UserIdInt int userId, int displayId) {
if (displayId == Display.INVALID_DISPLAY) {
return false;
}
@@ -220,12 +417,12 @@
return isCurrentUserOrRunningProfileOfCurrentUser(userId);
}
- // TODO(b/244644281): temporary workaround to let WM use this API without breaking current
+ // TODO(b/256242848): temporary workaround to let WM use this API without breaking current
// behavior - return true for current user / profile for any display (other than those
// explicitly assigned to another users), otherwise they wouldn't be able to launch
- // activities on other non-passenger displays, like cluster, display, or virtual displays).
+ // activities on other non-passenger displays, like cluster).
// In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
- // would be updated by DisplayManagerService when displays are created / initialized.
+ // would be updated by CarService to allow additional mappings.
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
synchronized (mLock) {
boolean assignedToUser = false;
@@ -261,7 +458,7 @@
/**
* See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
*/
- public int getDisplayAssignedToUser(int userId) {
+ public int getDisplayAssignedToUser(@UserIdInt int userId) {
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
return Display.DEFAULT_DISPLAY;
}
@@ -278,7 +475,7 @@
/**
* See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
*/
- public int getUserAssignedToDisplay(int displayId) {
+ public int getUserAssignedToDisplay(@UserIdInt int displayId) {
if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
return getCurrentUserId();
}
@@ -289,7 +486,7 @@
continue;
}
int userId = mUsersOnSecondaryDisplays.keyAt(i);
- if (!isProfileUnchecked(userId)) {
+ if (!isStartedProfile(userId)) {
return userId;
} else if (DBG) {
Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
@@ -306,24 +503,160 @@
return currentUserId;
}
+ /**
+ * Gets the ids of the visible users.
+ */
+ public IntArray getVisibleUsers() {
+ // TODO(b/258054362): this method's performance is O(n2), as it interacts through all users
+ // here, then again on isUserVisible(). We could "fix" it to be O(n), but given that the
+ // number of users is too small, the gain is probably not worth the increase on complexity.
+ IntArray visibleUsers = new IntArray();
+ synchronized (mLock) {
+ for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
+ int userId = mStartedProfileGroupIds.keyAt(i);
+ if (isUserVisible(userId)) {
+ visibleUsers.add(userId);
+ }
+ }
+ }
+ return visibleUsers;
+ }
+
+ /**
+ * Adds a {@link UserVisibilityListener listener}.
+ */
+ public void addListener(UserVisibilityListener listener) {
+ if (DBG) {
+ Slogf.d(TAG, "adding listener %s", listener);
+ }
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes a {@link UserVisibilityListener listener}.
+ */
+ public void removeListener(UserVisibilityListener listener) {
+ if (DBG) {
+ Slogf.d(TAG, "removing listener %s", listener);
+ }
+ synchronized (mLock) {
+ mListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Nofify all listeners about the visibility changes from before / after a change of state.
+ */
+ private void dispatchVisibilityChanged(IntArray visibleUsersBefore,
+ IntArray visibleUsersAfter) {
+ if (visibleUsersBefore == null) {
+ // Optimization - it's only null when listeners is empty
+ if (DBG) {
+ Slogf.d(TAG, "dispatchVisibilityChanged(): ignoring, no listeners");
+ }
+ return;
+ }
+ CopyOnWriteArrayList<UserVisibilityListener> listeners = mListeners;
+ if (DBG) {
+ Slogf.d(TAG,
+ "dispatchVisibilityChanged(): visibleUsersBefore=%s, visibleUsersAfter=%s, "
+ + "%d listeners (%s)", visibleUsersBefore, visibleUsersAfter, listeners.size(),
+ mListeners);
+ }
+ for (int i = 0; i < visibleUsersBefore.size(); i++) {
+ int userId = visibleUsersBefore.get(i);
+ if (visibleUsersAfter.indexOf(userId) == -1) {
+ dispatchVisibilityChanged(listeners, userId, /* visible= */ false);
+ }
+ }
+ for (int i = 0; i < visibleUsersAfter.size(); i++) {
+ int userId = visibleUsersAfter.get(i);
+ if (visibleUsersBefore.indexOf(userId) == -1) {
+ dispatchVisibilityChanged(listeners, userId, /* visible= */ true);
+ }
+ }
+ }
+
+ private void dispatchVisibilityChanged(CopyOnWriteArrayList<UserVisibilityListener> listeners,
+ @UserIdInt int userId, boolean visible) {
+ if (DBG) {
+ Slogf.d(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %d listeners",
+ userId, visible, listeners.size());
+ }
+ for (int i = 0; i < mListeners.size(); i++) {
+ UserVisibilityListener listener = mListeners.get(i);
+ if (DBG) {
+ Slogf.v(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %s",
+ userId, visible, listener);
+ }
+ mHandler.post(() -> listener.onUserVisibilityChanged(userId, visible));
+ }
+ }
+
private void dump(IndentingPrintWriter ipw) {
- ipw.println("UserVisibilityManager");
+ ipw.println("UserVisibilityMediator");
ipw.increaseIndent();
- ipw.print("Supports users on secondary displays: ");
- ipw.println(mUsersOnSecondaryDisplaysEnabled);
+ synchronized (mLock) {
+ ipw.print("Current user id: ");
+ ipw.println(mCurrentUserId);
- if (mUsersOnSecondaryDisplaysEnabled) {
- ipw.print("Users on secondary displays: ");
- synchronized (mLock) {
- ipw.println(mUsersOnSecondaryDisplays);
+ ipw.print("Visible users: ");
+ // TODO: merge 2 lines below if/when IntArray implements toString()...
+ IntArray visibleUsers = getVisibleUsers();
+ ipw.println(java.util.Arrays.toString(visibleUsers.toArray()));
+
+ dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
+ "u", "pg");
+
+ ipw.print("Supports background users on secondary displays: ");
+ ipw.println(mUsersOnSecondaryDisplaysEnabled);
+
+ if (mUsersOnSecondaryDisplays != null) {
+ dumpSparseIntArray(ipw, mUsersOnSecondaryDisplays,
+ "background user / secondary display", "u", "d");
+ }
+ int numberListeners = mListeners.size();
+ ipw.print("Number of listeners: ");
+ ipw.println(numberListeners);
+ if (numberListeners > 0) {
+ ipw.increaseIndent();
+ for (int i = 0; i < numberListeners; i++) {
+ ipw.print(i);
+ ipw.print(": ");
+ ipw.println(mListeners.get(i));
+ }
+ ipw.decreaseIndent();
}
}
ipw.decreaseIndent();
}
- void dump(PrintWriter pw) {
+ private static void dumpSparseIntArray(IndentingPrintWriter ipw, SparseIntArray array,
+ String arrayDescription, String keyName, String valueName) {
+ ipw.print("Number of ");
+ ipw.print(arrayDescription);
+ ipw.print(" mappings: ");
+ ipw.println(array.size());
+ if (array.size() <= 0) {
+ return;
+ }
+ ipw.increaseIndent();
+ for (int i = 0; i < array.size(); i++) {
+ ipw.print(keyName); ipw.print(':');
+ ipw.print(array.keyAt(i));
+ ipw.print(" -> ");
+ ipw.print(valueName); ipw.print(':');
+ ipw.println(array.valueAt(i));
+ }
+ ipw.decreaseIndent();
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String[] args) {
if (pw instanceof IndentingPrintWriter) {
dump((IndentingPrintWriter) pw);
return;
@@ -331,20 +664,56 @@
dump(new IndentingPrintWriter(pw));
}
- // TODO(b/244644281): remove methods below once this class caches that state
+ private static boolean isSpecialUserId(@UserIdInt int userId) {
+ switch (userId) {
+ case UserHandle.USER_ALL:
+ case UserHandle.USER_CURRENT:
+ case UserHandle.USER_CURRENT_OR_SELF:
+ case UserHandle.USER_NULL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isProfile(@UserIdInt int userId, @UserIdInt int profileGroupId) {
+ return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId;
+ }
+
+ // NOTE: methods below are needed because some APIs use the current users (full and profiles)
+ // state to decide whether a user is visible or not. If we decide to always store that info into
+ // mUsersOnSecondaryDisplays, we should remove them.
+
private @UserIdInt int getCurrentUserId() {
- return mService.getCurrentUserId();
+ synchronized (mLock) {
+ return mCurrentUserId;
+ }
}
private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
- return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ synchronized (mLock) {
+ // Special case as NO_PROFILE_GROUP_ID == USER_NULL
+ if (userId == USER_NULL || mCurrentUserId == USER_NULL) {
+ return false;
+ }
+ if (mCurrentUserId == userId) {
+ return true;
+ }
+ return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId;
+ }
}
- private boolean isProfileUnchecked(@UserIdInt int userId) {
- return mService.isProfileUnchecked(userId);
+ private boolean isStartedProfile(@UserIdInt int userId) {
+ int profileGroupId;
+ synchronized (mLock) {
+ profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+ }
+ return isProfile(userId, profileGroupId);
}
- private @UserIdInt int getProfileParentId(@UserIdInt int userId) {
- return mService.getProfileParentId(userId);
+ private @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index 905bcf9..1407530 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -320,12 +320,15 @@
public static class BackgroundDexoptJobStatsLogger {
/** Writes background dexopt job stats to statsd. */
public void write(@BackgroundDexOptService.Status int status,
- @JobParameters.StopReason int cancellationReason, long durationMs,
- long durationIncludingSleepMs) {
- ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
+ @JobParameters.StopReason int cancellationReason,
+ long durationMs) {
+ ArtStatsLog.write(
+ ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
STATUS_MAP.getOrDefault(status,
ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN),
- cancellationReason, durationMs, durationIncludingSleepMs);
+ cancellationReason,
+ durationMs,
+ 0); // deprecated, used to be durationIncludingSleepMs
}
}
}
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 5ba209d..9bca155 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -295,6 +295,7 @@
* NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
* for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
*/
+ @SuppressWarnings("ReturnValueIgnored")
/*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
classpath.getClass(); // Throw NPE if classpath is null
String classLoaderDexoptEncoding = classLoaderName;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8588267..83e17a5 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -86,6 +86,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -165,6 +166,11 @@
COARSE_BACKGROUND_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
+ private static final Set<String> FINE_LOCATION_PERMISSIONS = new ArraySet<>();
+ static {
+ FINE_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ }
+
private static final Set<String> ACTIVITY_RECOGNITION_PERMISSIONS = new ArraySet<>();
static {
ACTIVITY_RECOGNITION_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
@@ -616,6 +622,10 @@
grantPermissionsToSystemPackage(pm, getDefaultCaptivePortalLoginPackage(), userId,
NOTIFICATION_PERMISSIONS);
+ // Dock Manager
+ grantPermissionsToSystemPackage(pm, getDefaultDockManagerPackage(), userId,
+ NOTIFICATION_PERMISSIONS);
+
// Camera
grantPermissionsToSystemPackage(pm,
getDefaultSystemHandlerActivityPackage(pm, MediaStore.ACTION_IMAGE_CAPTURE, userId),
@@ -783,6 +793,8 @@
CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS,
PHONE_PERMISSIONS, SMS_PERMISSIONS, COARSE_BACKGROUND_LOCATION_PERMISSIONS,
NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS);
+ revokeRuntimePermissions(pm, voiceInteractPackageName, FINE_LOCATION_PERMISSIONS,
+ false, userId);
}
}
@@ -933,6 +945,10 @@
return mContext.getString(R.string.config_defaultCaptivePortalLoginPackageName);
}
+ private String getDefaultDockManagerPackage() {
+ return mContext.getString(R.string.config_defaultDockManagerPackageName);
+ }
+
@SafeVarargs
private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm,
ArrayList<String> packages, int userId, Set<String>... permissions) {
@@ -1924,7 +1940,7 @@
mPkgRequestingPerm, newRestrictionExcemptFlags, -1, mUser);
}
- if (newGranted != null && newGranted != mOriginalGranted) {
+ if (newGranted != null && !Objects.equals(newGranted, mOriginalGranted)) {
if (newGranted) {
NO_PM_CACHE.grantPermission(mPermission, mPkgRequestingPerm, mUser);
} else {
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 661161f..2a65a01 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -16,17 +16,18 @@
package com.android.server.pm.permission;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
-
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.permission.PermissionControllerManager;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -48,7 +49,7 @@
"one_time_permissions_killed_delay_millis";
private final @NonNull Context mContext;
- private final @NonNull ActivityManager mActivityManager;
+ private final @NonNull IActivityManager mIActivityManager;
private final @NonNull AlarmManager mAlarmManager;
private final @NonNull PermissionControllerManager mPermissionControllerManager;
@@ -78,50 +79,15 @@
OneTimePermissionUserManager(@NonNull Context context) {
mContext = context;
- mActivityManager = context.getSystemService(ActivityManager.class);
+ mIActivityManager = ActivityManager.getService();
mAlarmManager = context.getSystemService(AlarmManager.class);
mPermissionControllerManager = new PermissionControllerManager(
mContext, PermissionThread.getHandler());
mHandler = context.getMainThreadHandler();
}
- /**
- * Starts a one-time permission session for a given package. A one-time permission session is
- * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
- * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
- * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
- * until going > importanceToResetTimer.
- * <p>
- * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
- * then the session is extended until either the importance goes above
- * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
- * will continue the session and reset the timer.
- * </p>
- * <p>
- * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
- * </p>
- * <p>
- * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout
- * is invoked.
- * </p>
- * <p>
- * Note that if there is currently an active session for a package a new one isn't created and
- * the existing one isn't changed.
- * </p>
- * @param packageName The package to start a one-time permission session for
- * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
- * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies
- * before ending the session. Set to -1 to use default value
- * for the device.
- * @param importanceToResetTimer The least important level to uid must be to reset the timer
- * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
- * session alive
- *
- * @hide
- */
void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis,
- long revokeAfterKilledDelayMillis, int importanceToResetTimer,
- int importanceToKeepSessionAlive) {
+ long revokeAfterKilledDelayMillis) {
int uid;
try {
uid = mContext.getPackageManager().getPackageUid(packageName, 0);
@@ -133,13 +99,11 @@
synchronized (mLock) {
PackageInactivityListener listener = mListeners.get(uid);
if (listener != null) {
- listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis,
- importanceToResetTimer, importanceToKeepSessionAlive);
+ listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis);
return;
}
listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
- revokeAfterKilledDelayMillis, importanceToResetTimer,
- importanceToKeepSessionAlive);
+ revokeAfterKilledDelayMillis);
mListeners.put(uid, listener);
}
}
@@ -184,34 +148,58 @@
private static final long TIMER_INACTIVE = -1;
+ private static final int STATE_GONE = 0;
+ private static final int STATE_TIMER = 1;
+ private static final int STATE_ACTIVE = 2;
+
private final int mUid;
private final @NonNull String mPackageName;
private long mTimeout;
private long mRevokeAfterKilledDelay;
- private int mImportanceToResetTimer;
- private int mImportanceToKeepSessionAlive;
private boolean mIsAlarmSet;
private boolean mIsFinished;
private long mTimerStart = TIMER_INACTIVE;
- private final ActivityManager.OnUidImportanceListener mStartTimerListener;
- private final ActivityManager.OnUidImportanceListener mSessionKillableListener;
- private final ActivityManager.OnUidImportanceListener mGoneListener;
-
private final Object mInnerLock = new Object();
private final Object mToken = new Object();
+ private final IUidObserver.Stub mObserver = new IUidObserver.Stub() {
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ if (uid == mUid) {
+ PackageInactivityListener.this.updateUidState(STATE_GONE);
+ }
+ }
+
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq,
+ int capability) {
+ if (uid == mUid) {
+ if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ PackageInactivityListener.this.updateUidState(STATE_TIMER);
+ } else {
+ PackageInactivityListener.this.updateUidState(STATE_ACTIVE);
+ }
+ }
+ }
+
+ public void onUidActive(int uid) {
+ }
+ public void onUidIdle(int uid, boolean disabled) {
+ }
+ public void onUidProcAdjChanged(int uid) {
+ }
+ public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ };
private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
- long revokeAfterkilledDelay, int importanceToResetTimer,
- int importanceToKeepSessionAlive) {
-
+ long revokeAfterkilledDelay) {
Log.i(LOG_TAG,
"Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
- + " killedDelay=" + revokeAfterkilledDelay
- + " importanceToResetTimer=" + importanceToResetTimer
- + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
+ + " killedDelay=" + revokeAfterkilledDelay);
mUid = uid;
mPackageName = packageName;
@@ -221,27 +209,24 @@
DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY,
DEFAULT_KILLED_DELAY_MILLIS)
: revokeAfterkilledDelay;
- mImportanceToResetTimer = importanceToResetTimer;
- mImportanceToKeepSessionAlive = importanceToKeepSessionAlive;
- mStartTimerListener =
- (changingUid, importance) -> onImportanceChanged(changingUid, importance);
- mSessionKillableListener =
- (changingUid, importance) -> onImportanceChanged(changingUid, importance);
- mGoneListener =
- (changingUid, importance) -> onImportanceChanged(changingUid, importance);
+ try {
+ mIActivityManager.registerUidObserver(mObserver,
+ ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ null);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Couldn't check uid proc state", e);
+ // Can't register uid observer, just revoke immediately
+ synchronized (mInnerLock) {
+ onPackageInactiveLocked();
+ }
+ }
- mActivityManager.addOnUidImportanceListener(mStartTimerListener,
- importanceToResetTimer);
- mActivityManager.addOnUidImportanceListener(mSessionKillableListener,
- importanceToKeepSessionAlive);
- mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED);
-
- onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName));
+ updateUidState();
}
- public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis,
- int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) {
synchronized (mInnerLock) {
mTimeout = Math.min(mTimeout, timeoutMillis);
mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay,
@@ -250,63 +235,79 @@
DeviceConfig.NAMESPACE_PERMISSIONS,
PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS)
: revokeAfterKilledDelayMillis);
- mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer);
- mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive,
- mImportanceToKeepSessionAlive);
Log.v(LOG_TAG,
"Updated params for " + mPackageName + ". timeout=" + mTimeout
- + " killedDelay=" + mRevokeAfterKilledDelay
- + " importanceToResetTimer=" + mImportanceToResetTimer
- + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive);
- onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName));
+ + " killedDelay=" + mRevokeAfterKilledDelay);
+ updateUidState();
}
}
- private void onImportanceChanged(int uid, int importance) {
- if (uid != mUid) {
- return;
+ private int getCurrentState() {
+ try {
+ return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null));
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Couldn't check uid proc state", e);
}
+ return STATE_GONE;
+ }
- Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
- + " importance=" + importance);
+ private int getStateFromProcState(int procState) {
+ if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ return STATE_GONE;
+ } else {
+ if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ return STATE_TIMER;
+ } else {
+ return STATE_ACTIVE;
+ }
+ }
+ }
+
+ private void updateUidState() {
+ updateUidState(getCurrentState());
+ }
+
+ private void updateUidState(int state) {
+ Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")."
+ + " state=" + state);
synchronized (mInnerLock) {
// Remove any pending inactivity callback
mHandler.removeCallbacksAndMessages(mToken);
- if (importance > IMPORTANCE_CACHED) {
+ if (state == STATE_GONE) {
if (mRevokeAfterKilledDelay == 0) {
onPackageInactiveLocked();
return;
}
// Delay revocation in case app is restarting
mHandler.postDelayed(() -> {
- int imp = mActivityManager.getUidImportance(mUid);
- if (imp > IMPORTANCE_CACHED) {
- onPackageInactiveLocked();
- } else {
- if (DEBUG) {
- Log.d(LOG_TAG, "No longer gone after delayed revocation. "
- + "Rechecking for " + mPackageName + " (" + mUid + ").");
+ int currentState;
+ synchronized (mInnerLock) {
+ currentState = getCurrentState();
+ if (currentState == STATE_GONE) {
+ onPackageInactiveLocked();
+ return;
}
- onImportanceChanged(mUid, imp);
}
+ if (DEBUG) {
+ Log.d(LOG_TAG, "No longer gone after delayed revocation. "
+ + "Rechecking for " + mPackageName + " (" + mUid
+ + ").");
+ }
+ updateUidState(currentState);
}, mToken, mRevokeAfterKilledDelay);
return;
- }
- if (importance > mImportanceToResetTimer) {
+ } else if (state == STATE_TIMER) {
if (mTimerStart == TIMER_INACTIVE) {
if (DEBUG) {
Log.d(LOG_TAG, "Start the timer for "
+ mPackageName + " (" + mUid + ").");
}
mTimerStart = System.currentTimeMillis();
+ setAlarmLocked();
}
- } else {
+ } else if (state == STATE_ACTIVE) {
mTimerStart = TIMER_INACTIVE;
- }
- if (importance > mImportanceToKeepSessionAlive) {
- setAlarmLocked();
- } else {
cancelAlarmLocked();
}
}
@@ -320,19 +321,9 @@
mIsFinished = true;
cancelAlarmLocked();
try {
- mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
- } catch (IllegalArgumentException e) {
- Log.e(LOG_TAG, "Could not remove start timer listener", e);
- }
- try {
- mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
- } catch (IllegalArgumentException e) {
- Log.e(LOG_TAG, "Could not remove session killable listener", e);
- }
- try {
- mActivityManager.removeOnUidImportanceListener(mGoneListener);
- } catch (IllegalArgumentException e) {
- Log.e(LOG_TAG, "Could not remove gone listener", e);
+ mIActivityManager.unregisterUidObserver(mObserver);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
}
}
}
@@ -396,9 +387,11 @@
mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
mPackageName);
});
- mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
- mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
- mActivityManager.removeOnUidImportanceListener(mGoneListener);
+ try {
+ mIActivityManager.unregisterUidObserver(mObserver);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
+ }
synchronized (mLock) {
mListeners.remove(mUid);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 1fa3b3b..9ec63fc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -50,6 +50,7 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.healthconnect.HealthConnectManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
@@ -385,8 +386,7 @@
@Override
public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
- long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer,
- int importanceToKeepSessionAlive) {
+ long timeoutMillis, long revokeAfterKilledDelayMillis) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
"Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
@@ -396,8 +396,7 @@
final long token = Binder.clearCallingIdentity();
try {
getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
- timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
- importanceToKeepSessionAlive);
+ timeoutMillis, revokeAfterKilledDelayMillis);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1102,7 +1101,7 @@
if (resolvedPackageName == null) {
return;
}
- appOpsManager.finishOp(accessorSource.getToken(), op,
+ appOpsManager.finishOp(attributionSourceState.token, op,
accessorSource.getUid(), resolvedPackageName,
accessorSource.getAttributionTag());
} else {
@@ -1111,8 +1110,9 @@
if (resolvedAttributionSource.getPackageName() == null) {
return;
}
- appOpsManager.finishProxyOp(AppOpsManager.opToPublicName(op),
- resolvedAttributionSource, skipCurrentFinish);
+ appOpsManager.finishProxyOp(attributionSourceState.token,
+ AppOpsManager.opToPublicName(op), resolvedAttributionSource,
+ skipCurrentFinish);
}
RegisteredAttribution registered =
sRunningAttributionSources.remove(current.getToken());
@@ -1158,7 +1158,8 @@
if (permissionInfo == null) {
try {
permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
- if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+ if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)
+ || HealthConnectManager.isHealthPermission(context, permission)) {
// Double addition due to concurrency is fine - the backing
// store is concurrent.
sPlatformPermissions.put(permission, permissionInfo);
@@ -1227,10 +1228,11 @@
&& next.getNext() == null);
final boolean selfAccess = singleReceiverFromDatasource || next == null;
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
- selfAccess, singleReceiverFromDatasource, AppOpsManager.OP_NONE,
- AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+ current, message, forDataDelivery, /*startDataDelivery*/ false,
+ skipCurrentChecks, selfAccess, singleReceiverFromDatasource,
+ AppOpsManager.OP_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE,
AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
switch (opMode) {
@@ -1333,10 +1335,10 @@
attributionSource, next, fromDatasource, startDataDelivery, selfAccess,
isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
- singleReceiverFromDatasource, attributedOp, proxyAttributionFlags,
- proxiedAttributionFlags, attributionChainId);
+ final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+ current, message, forDataDelivery, startDataDelivery, skipCurrentChecks,
+ selfAccess, singleReceiverFromDatasource, attributedOp,
+ proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
@@ -1481,8 +1483,8 @@
attributionSource, next, /*fromDatasource*/ false, startDataDelivery,
selfAccess, isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ final int opMode = performOpTransaction(context, current.getToken(), op, current,
+ message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
/*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
@@ -1504,7 +1506,8 @@
}
@SuppressWarnings("ConstantConditions")
- private static int performOpTransaction(@NonNull Context context, int op,
+ private static int performOpTransaction(@NonNull Context context,
+ @NonNull IBinder chainStartToken, int op,
@NonNull AttributionSource attributionSource, @Nullable String message,
boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp,
@@ -1566,7 +1569,7 @@
if (selfAccess) {
try {
startedOpResult = appOpsManager.startOpNoThrow(
- resolvedAttributionSource.getToken(), startedOp,
+ chainStartToken, startedOp,
resolvedAttributionSource.getUid(),
resolvedAttributionSource.getPackageName(),
/*startIfModeDefault*/ false,
@@ -1577,14 +1580,14 @@
+ " platform defined runtime permission "
+ AppOpsManager.opToPermission(op) + " while not having "
+ Manifest.permission.UPDATE_APP_OPS_STATS);
- startedOpResult = appOpsManager.startProxyOpNoThrow(attributedOp,
- attributionSource, message, skipProxyOperation,
+ startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+ attributedOp, attributionSource, message, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
} else {
try {
- startedOpResult = appOpsManager.startProxyOpNoThrow(startedOp,
- resolvedAttributionSource, message, skipProxyOperation,
+ startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+ startedOp, resolvedAttributionSource, message, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
} catch (SecurityException e) {
//TODO 195339480: remove
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index ab223ef..5ffbbdc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -231,6 +231,7 @@
READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
+ READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 595c34c..f80ead6 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -1209,6 +1209,7 @@
public void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
@Nullable String packageName, @Nullable @UserIdInt Integer userId)
throws NameNotFoundException {
+ mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
final Computer snapshot = mConnection.snapshot();
synchronized (mLock) {
if (packageName == null) {
@@ -1257,6 +1258,7 @@
@Override
public void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
@NonNull List<String> domains, @Nullable @UserIdInt Integer userId) {
+ mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
final Computer snapshot = mConnection.snapshot();
synchronized (mLock) {
int size = domains.size();
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index a6d148c..383249f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -45,13 +45,11 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintConsumer;
import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
import com.android.server.LocalServices;
@@ -257,14 +255,14 @@
}
@Override
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId,
- @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
- Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
- return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+ @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String,
+ Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+ return superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
@@ -280,10 +278,10 @@
}
@Override
- public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
- Boolean, Void> superImpl) {
- superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+ @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl) {
+ superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, skipProxyOperation);
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index f8fcaff..b1dee49 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -96,6 +96,7 @@
private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
+ private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@@ -149,6 +150,8 @@
case FLAG_APP_INACCESSIBLE:
flags |= DeviceState.FLAG_APP_INACCESSIBLE;
break;
+ case FLAG_EMULATED_ONLY:
+ flags |= DeviceState.FLAG_EMULATED_ONLY;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
@@ -225,7 +228,13 @@
}
final Conditions conditions = stateConditions.get(i);
if (conditions == null) {
- mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+ // If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered
+ // by a physical hardware change, and should always return false for it's conditions
+ if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) {
+ mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
+ } else {
+ mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+ }
continue;
}
@@ -358,7 +367,7 @@
return;
}
- int newState = mOrderedStates[0].getIdentifier();
+ int newState = INVALID_DEVICE_STATE;
for (int i = 0; i < mOrderedStates.length; i++) {
int state = mOrderedStates[i].getIdentifier();
if (DEBUG) {
@@ -387,7 +396,7 @@
}
}
- if (newState != mLastReportedState) {
+ if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
mLastReportedState = newState;
stateToReport = newState;
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index ffb652e..e61effa 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -58,6 +58,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PermissionInfo;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
@@ -78,7 +79,6 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseLongArray;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -146,7 +146,7 @@
* scheduled for a package/user.
*/
@GuardedBy("mLock")
- private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>();
+ private final ArraySet<UserPackage> mIsPackageSyncsScheduled = new ArraySet<>();
/**
* Whether an async {@link #resetAppOpPermissionsIfNotRequestedForUid} is currently
@@ -223,9 +223,11 @@
this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
mAppOpsCallback = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, String packageName) {
- synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
- UserHandle.getUserId(uid));
+ public void opChanged(int op, int uid, @Nullable String packageName) {
+ if (packageName != null) {
+ synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
+ UserHandle.getUserId(uid));
+ }
resetAppOpPermissionsIfNotRequestedForUidAsync(uid);
}
};
@@ -372,7 +374,7 @@
@UserIdInt int changedUserId) {
if (isStarted(changedUserId)) {
synchronized (mLock) {
- if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
+ if (mIsPackageSyncsScheduled.add(UserPackage.of(changedUserId, packageName))) {
// TODO(b/165030092): migrate this to PermissionThread.getHandler().
// synchronizePackagePermissionsAndAppOpsForUser is a heavy operation.
// Dispatched on a PermissionThread, it interferes with user switch.
@@ -640,7 +642,7 @@
private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName,
@UserIdInt int userId) {
synchronized (mLock) {
- mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId));
+ mIsPackageSyncsScheduled.remove(UserPackage.of(userId, packageName));
}
if (DEBUG) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 98b5c1b..3aa333a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3589,7 +3589,12 @@
@Override
public void onKeyguardExitResult(boolean success) {
if (success) {
- startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
}
});
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 8582f54..2d76c50 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -127,7 +127,7 @@
*/
public void notifyPowerPressed() {
Log.i(TAG, "notifyPowerPressed");
- if (mFingerprintManager == null) {
+ if (mFingerprintManager == null && mSideFpsEventHandlerReady.get()) {
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
}
if (mFingerprintManager == null) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d8b1120..9281f4b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -423,6 +423,9 @@
// The current battery level percentage.
private int mBatteryLevel;
+ // The amount of battery drained while the device has been in a dream state.
+ private int mDreamsBatteryLevelDrain;
+
// True if updatePowerStateLocked() is already in progress.
// TODO(b/215518989): Remove this once transactions are in place
private boolean mUpdatePowerStateInProgress;
@@ -455,11 +458,6 @@
@GuardedBy("mEnhancedDischargeTimeLock")
private boolean mEnhancedDischargePredictionIsPersonalized;
- // The battery level percentage at the time the dream started.
- // This is used to terminate a dream and go to sleep if the battery is
- // draining faster than it is charging and the user activity timeout has expired.
- private int mBatteryLevelWhenDreamStarted;
-
// The current dock state.
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -472,9 +470,6 @@
// True if the device should wake up when plugged or unplugged.
private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
- // True if the device should keep dreaming when undocked.
- private boolean mKeepDreamingWhenUndockingConfig;
-
// True if the device should wake up when plugged or unplugged in theater mode.
private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig;
@@ -681,6 +676,19 @@
// but the DreamService has not yet been told to start (it's an async process).
private boolean mDozeStartInProgress;
+ // Whether to keep dreaming when the device is undocked.
+ private boolean mKeepDreamingWhenUndocked;
+
+ private final class DreamManagerStateListener implements
+ DreamManagerInternal.DreamManagerStateListener {
+ @Override
+ public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+ synchronized (mLock) {
+ mKeepDreamingWhenUndocked = keepDreaming;
+ }
+ }
+ }
+
private final class PowerGroupWakefulnessChangeListener implements
PowerGroup.PowerGroupListener {
@GuardedBy("mLock")
@@ -1053,7 +1061,7 @@
super(context);
mContext = context;
- mBinderService = new BinderService();
+ mBinderService = new BinderService(mContext);
mLocalService = new LocalService();
mNativeWrapper = injector.createNativeWrapper();
mSystemProperties = injector.createSystemPropertiesWrapper();
@@ -1285,6 +1293,9 @@
new DisplayGroupPowerChangeListener();
mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
+ // This DreamManager method does not acquire a lock, so it should be safe to call.
+ mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+
mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
mInjector.createSuspendBlocker(
this, "PowerManagerService.WirelessChargerDetector"),
@@ -1402,8 +1413,6 @@
com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay);
mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean(
com.android.internal.R.bool.config_unplugTurnsOnScreen);
- mKeepDreamingWhenUndockingConfig = resources.getBoolean(
- com.android.internal.R.bool.config_keepDreamingWhenUndocking);
mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug);
mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
@@ -2458,15 +2467,25 @@
final int oldPlugType = mPlugType;
mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
mPlugType = mBatteryManagerInternal.getPlugType();
+ final int oldBatteryLevel = mBatteryLevel;
mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();
+ final boolean isOverheat = mBatteryManagerInternal.getBatteryHealth()
+ == BatteryManager.BATTERY_HEALTH_OVERHEAT;
if (DEBUG_SPEW) {
Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
+ ", mIsPowered=" + mIsPowered
+ ", oldPlugType=" + oldPlugType
+ ", mPlugType=" + mPlugType
- + ", mBatteryLevel=" + mBatteryLevel);
+ + ", oldBatteryLevel=" + oldBatteryLevel
+ + ", mBatteryLevel=" + mBatteryLevel
+ + ", isOverheat=" + isOverheat);
+ }
+
+ if (!isOverheat && oldBatteryLevel > 0
+ && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING) {
+ mDreamsBatteryLevelDrain += (oldBatteryLevel - mBatteryLevel);
}
if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
@@ -2518,7 +2537,7 @@
}
// Don't wake when undocking while dreaming if configured not to.
- if (mKeepDreamingWhenUndockingConfig
+ if (mKeepDreamingWhenUndocked
&& getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
&& wasPowered && !mIsPowered
&& oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
@@ -3289,7 +3308,7 @@
// Remember the initial battery level when the dream started.
if (startDreaming && isDreaming) {
- mBatteryLevelWhenDreamStarted = mBatteryLevel;
+ mDreamsBatteryLevelDrain = 0;
if (wakefulness == WAKEFULNESS_DOZING) {
Slog.i(TAG, "Dozing...");
} else {
@@ -3310,16 +3329,15 @@
if (wakefulness == WAKEFULNESS_DREAMING) {
if (isDreaming && canDreamLocked(powerGroup)) {
if (mDreamsBatteryLevelDrainCutoffConfig >= 0
- && mBatteryLevel < mBatteryLevelWhenDreamStarted
- - mDreamsBatteryLevelDrainCutoffConfig
+ && mDreamsBatteryLevelDrain > mDreamsBatteryLevelDrainCutoffConfig
&& !isBeingKeptAwakeLocked(powerGroup)) {
// If the user activity timeout expired and the battery appears
// to be draining faster than it is charging then stop dreaming
// and go to sleep.
Slog.i(TAG, "Stopping dream because the battery appears to "
+ "be draining faster than it is charging. "
- + "Battery level when dream started: "
- + mBatteryLevelWhenDreamStarted + "%. "
+ + "Battery level drained while dreaming: "
+ + mDreamsBatteryLevelDrain + "%. "
+ "Battery level now: " + mBatteryLevel + "%.");
} else {
return; // continue dreaming
@@ -3550,6 +3568,11 @@
mScreenBrightnessBoostInProgress);
}
+ @VisibleForTesting
+ int getDreamsBatteryLevelDrain() {
+ return mDreamsBatteryLevelDrain;
+ }
+
private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks =
new DisplayManagerInternal.DisplayPowerCallbacks() {
@@ -4394,7 +4417,7 @@
pw.println(" mIsPowered=" + mIsPowered);
pw.println(" mPlugType=" + mPlugType);
pw.println(" mBatteryLevel=" + mBatteryLevel);
- pw.println(" mBatteryLevelWhenDreamStarted=" + mBatteryLevelWhenDreamStarted);
+ pw.println(" mDreamsBatteryLevelDrain=" + mDreamsBatteryLevelDrain);
pw.println(" mDockState=" + mDockState);
pw.println(" mStayOn=" + mStayOn);
pw.println(" mProximityPositive=" + mProximityPositive);
@@ -4472,8 +4495,7 @@
+ mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
pw.println(" mTheaterModeEnabled="
+ mTheaterModeEnabled);
- pw.println(" mKeepDreamingWhenUndockingConfig="
- + mKeepDreamingWhenUndockingConfig);
+ pw.println(" mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked);
pw.println(" mSuspendWhenScreenOffDueToProximityConfig="
+ mSuspendWhenScreenOffDueToProximityConfig);
pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig);
@@ -4640,8 +4662,8 @@
proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
proto.write(
- PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
- mBatteryLevelWhenDreamStarted);
+ PowerManagerServiceDumpProto.BATTERY_LEVEL_DRAINED_WHILE_DREAMING,
+ mDreamsBatteryLevelDrain);
proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
@@ -5485,12 +5507,17 @@
@VisibleForTesting
final class BinderService extends IPowerManager.Stub {
+ private final PowerManagerShellCommand mShellCommand;
+
+ BinderService(Context context) {
+ mShellCommand = new PowerManagerShellCommand(context, this);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new PowerManagerShellCommand(this)).exec(
- this, in, out, err, args, callback, resultReceiver);
+ mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index a9b33ed..9439b76 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,10 +16,15 @@
package com.android.server.power;
+import android.content.Context;
import android.content.Intent;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ShellCommand;
+import android.util.SparseArray;
+import android.view.Display;
import java.io.PrintWriter;
import java.util.List;
@@ -27,9 +32,13 @@
class PowerManagerShellCommand extends ShellCommand {
private static final int LOW_POWER_MODE_ON = 1;
- final PowerManagerService.BinderService mService;
+ private final Context mContext;
+ private final PowerManagerService.BinderService mService;
- PowerManagerShellCommand(PowerManagerService.BinderService service) {
+ private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
+
+ PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
+ mContext = context;
mService = service;
}
@@ -52,6 +61,8 @@
return runSuppressAmbientDisplay();
case "list-ambient-display-suppression-tokens":
return runListAmbientDisplaySuppressionTokens();
+ case "set-prox":
+ return runSetProx();
default:
return handleDefaultCommands(cmd);
}
@@ -117,6 +128,56 @@
return 0;
}
+
+ /** TODO: Consider updating this code to support all wakelock types. */
+ private int runSetProx() throws RemoteException {
+ PrintWriter pw = getOutPrintWriter();
+ final boolean acquire;
+ switch (getNextArgRequired().toLowerCase()) {
+ case "list":
+ pw.println("Wakelocks:");
+ pw.println(mProxWakelocks);
+ return 0;
+ case "acquire":
+ acquire = true;
+ break;
+ case "release":
+ acquire = false;
+ break;
+ default:
+ pw.println("Error: Allowed options are 'list' 'enable' and 'disable'.");
+ return -1;
+ }
+
+ int displayId = Display.INVALID_DISPLAY;
+ String displayOption = getNextArg();
+ if ("-d".equals(displayOption)) {
+ String idStr = getNextArg();
+ displayId = Integer.parseInt(idStr);
+ if (displayId < 0) {
+ pw.println("Error: Specified displayId (" + idStr + ") must a non-negative int.");
+ return -1;
+ }
+ }
+
+ int wakelockIndex = displayId + 1; // SparseArray doesn't support negative indexes
+ WakeLock wakelock = mProxWakelocks.get(wakelockIndex);
+ if (wakelock == null) {
+ PowerManager pm = mContext.getSystemService(PowerManager.class);
+ wakelock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
+ "PowerManagerShellCommand[" + displayId + "]", displayId);
+ mProxWakelocks.put(wakelockIndex, wakelock);
+ }
+
+ if (acquire) {
+ wakelock.acquire();
+ } else {
+ wakelock.release();
+ }
+ pw.println(wakelock);
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -138,6 +199,11 @@
pw.println(" ambient display");
pw.println(" list-ambient-display-suppression-tokens");
pw.println(" prints the tokens used to suppress ambient display");
+ pw.println(" set-prox [list|acquire|release] (-d <display_id>)");
+ pw.println(" Acquires the proximity sensor wakelock. Wakelock is associated with");
+ pw.println(" a specific display if specified. 'list' lists wakelocks previously");
+ pw.println(" created by set-prox including their held status.");
+
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index dfa1281..0d13831 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.IHintManager;
import android.os.IHintSession;
+import android.os.PerformanceHintManager;
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -147,6 +148,8 @@
private static native void nativeReportActualWorkDuration(
long halPtr, long[] actualDurationNanos, long[] timeStampNanos);
+ private static native void nativeSendHint(long halPtr, int hint);
+
private static native long nativeGetHintSessionPreferredRate();
/** Wrapper for HintManager.nativeInit */
@@ -186,6 +189,11 @@
timeStampNanos);
}
+ /** Wrapper for HintManager.sendHint */
+ public void halSendHint(long halPtr, int hint) {
+ nativeSendHint(halPtr, hint);
+ }
+
/** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
public long halGetHintSessionPreferredRate() {
return nativeGetHintSessionPreferredRate();
@@ -475,6 +483,18 @@
}
}
+ @Override
+ public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
+ synchronized (mLock) {
+ if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+ return;
+ }
+ Preconditions.checkArgument(hint >= 0, "the hint ID the hint value should be"
+ + " greater than zero.");
+ mNativeWrapper.halSendHint(mHalSessionPtr, hint);
+ }
+ }
+
private void onProcStateChanged() {
updateHintAllowed();
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 916df89..0c5e451 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -11507,6 +11507,9 @@
mHistory.reset();
+ // Store the empty state to disk to ensure consistency
+ writeSyncLocked();
+
// Flush external data, gathering snapshots, but don't process it since it is pre-reset data
mIgnoreNextExternalStats = true;
mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET);
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index d9f504e..ac97038 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -206,16 +206,21 @@
Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
}
+ mRebinder = null;
+
Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
mBoundServiceInfo.getComponentName());
- if (!mContext.bindServiceAsUser(bindIntent, this,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
- mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
- Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
- mRebinder = this::bind;
- mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
- } else {
- mRebinder = null;
+ try {
+ if (!mContext.bindServiceAsUser(bindIntent, this,
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+ mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+ Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+ mRebinder = this::bind;
+ mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+ }
+ } catch (SecurityException e) {
+ // if anything goes wrong it shouldn't crash the system server
+ Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " bind failed", e);
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7281a47..50eab256 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -272,7 +272,6 @@
mContext = context;
LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
- LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
// We always have a default display.
final UiState state = new UiState();
@@ -289,6 +288,17 @@
mSessionMonitor = new SessionMonitor(mContext);
}
+ /**
+ * Publish the {@link GlobalActionsProvider}.
+ */
+ // TODO(b/259420401): investigate if we can extract GlobalActionsProvider to its own system
+ // service.
+ public void publishGlobalActionsProvider() {
+ if (LocalServices.getService(GlobalActionsProvider.class) == null) {
+ LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
+ }
+ }
+
private IOverlayManager getOverlayManager() {
// No need to synchronize; worst-case scenario it will be fetched twice.
if (mOverlayManager == null) {
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 8d106f7..5801920 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -128,4 +128,5 @@
@Override
public void dumpDebugLog(@NonNull PrintWriter printWriter) {
SystemClockTime.dump(printWriter);
- }}
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
index 8218fa5..80d9599 100644
--- a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
+++ b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
@@ -19,20 +19,15 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.ShellCommand;
-import android.os.SystemClock;
-import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.StringTokenizer;
/**
- * A time zone suggestion from the location_time_zone_manager service to the time_zone_detector
- * service.
+ * A time zone suggestion from the location_time_zone_manager service (AKA the location-based time
+ * zone detection algorithm).
*
* <p>Geolocation-based suggestions have the following properties:
*
@@ -63,24 +58,16 @@
* location_time_zone_manager may become uncertain if components further downstream cannot
* determine the device's location with sufficient accuracy, or if the location is known but no
* time zone can be determined because no time zone mapping information is available.</li>
- * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
- * used to record why the suggestion exists and how it was obtained. This information exists
- * only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use
- * in detection logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
* </li>
* </ul>
- *
- * @hide
*/
public final class GeolocationTimeZoneSuggestion {
@ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis;
@Nullable private final List<String> mZoneIds;
- @Nullable private ArrayList<String> mDebugInfo;
private GeolocationTimeZoneSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @Nullable List<String> zoneIds) {
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis, @Nullable List<String> zoneIds) {
mEffectiveFromElapsedMillis = effectiveFromElapsedMillis;
if (zoneIds == null) {
// Unopinionated
@@ -104,8 +91,7 @@
*/
@NonNull
public static GeolocationTimeZoneSuggestion createCertainSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @NonNull List<String> zoneIds) {
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis, @NonNull List<String> zoneIds) {
return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, zoneIds);
}
@@ -126,25 +112,6 @@
return mZoneIds;
}
- /** Returns debug information. See {@link GeolocationTimeZoneSuggestion} for details. */
- @NonNull
- public List<String> getDebugInfo() {
- return mDebugInfo == null
- ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
- }
-
- /**
- * Associates information with the instance that can be useful for debugging / logging. The
- * information is present in {@link #toString()} but is not considered for
- * {@link #equals(Object)} and {@link #hashCode()}.
- */
- public void addDebugInfo(String... debugInfos) {
- if (mDebugInfo == null) {
- mDebugInfo = new ArrayList<>();
- }
- mDebugInfo.addAll(Arrays.asList(debugInfos));
- }
-
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -169,59 +136,6 @@
return "GeolocationTimeZoneSuggestion{"
+ "mEffectiveFromElapsedMillis=" + mEffectiveFromElapsedMillis
+ ", mZoneIds=" + mZoneIds
- + ", mDebugInfo=" + mDebugInfo
+ '}';
}
-
- /** @hide */
- public static GeolocationTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) {
- String zoneIdsString = null;
- String opt;
- while ((opt = cmd.getNextArg()) != null) {
- switch (opt) {
- case "--zone_ids": {
- zoneIdsString = cmd.getNextArgRequired();
- break;
- }
- default: {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
- }
-
- if (zoneIdsString == null) {
- throw new IllegalArgumentException("Missing --zone_ids");
- }
-
- long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
- List<String> zoneIds = parseZoneIdsArg(zoneIdsString);
- GeolocationTimeZoneSuggestion suggestion =
- new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds);
- suggestion.addDebugInfo("Command line injection");
- return suggestion;
- }
-
- private static List<String> parseZoneIdsArg(String zoneIdsString) {
- if ("UNCERTAIN".equals(zoneIdsString)) {
- return null;
- } else if ("EMPTY".equals(zoneIdsString)) {
- return Collections.emptyList();
- } else {
- ArrayList<String> zoneIds = new ArrayList<>();
- StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
- while (tokenizer.hasMoreTokens()) {
- zoneIds.add(tokenizer.nextToken());
- }
- return zoneIds;
- }
- }
-
- /** @hide */
- public static void printCommandLineOpts(@NonNull PrintWriter pw) {
- pw.println("Geolocation suggestion options:");
- pw.println(" --zone_ids {UNCERTAIN|EMPTY|<Olson ID>+}");
- pw.println();
- pw.println("See " + GeolocationTimeZoneSuggestion.class.getName()
- + " for more information");
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
new file mode 100644
index 0000000..1ffd9a1
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
@@ -0,0 +1,194 @@
+/*
+ * 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.server.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/**
+ * An event from the location_time_zone_manager service (AKA the location-based time zone detection
+ * algorithm). An event can represent a new time zone recommendation, an algorithm status change, or
+ * both.
+ *
+ * <p>Events have the following properties:
+ *
+ * <ul>
+ * <li>{@code algorithmStatus}: The current status of the location-based time zone detection
+ * algorithm.</li>
+ * <li>{@code suggestion}: The latest time zone suggestion, if there is one.</li>
+ * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
+ * used to record why the event exists and how information contained within it was obtained.
+ * This information exists only to aid in debugging and therefore is used by
+ * {@link #toString()}, but it is not for use in detection logic and is not considered in
+ * {@link #hashCode()} or {@link #equals(Object)}.
+ * </li>
+ * </ul>
+ */
+public final class LocationAlgorithmEvent {
+
+ @NonNull private final LocationTimeZoneAlgorithmStatus mAlgorithmStatus;
+ @Nullable private final GeolocationTimeZoneSuggestion mSuggestion;
+ @Nullable private ArrayList<String> mDebugInfo;
+
+ /** Creates a new instance. */
+ public LocationAlgorithmEvent(
+ @NonNull LocationTimeZoneAlgorithmStatus algorithmStatus,
+ @Nullable GeolocationTimeZoneSuggestion suggestion) {
+ mAlgorithmStatus = Objects.requireNonNull(algorithmStatus);
+ mSuggestion = suggestion;
+ }
+
+ /**
+ * Returns the status of the location time zone detector algorithm.
+ */
+ @NonNull
+ public LocationTimeZoneAlgorithmStatus getAlgorithmStatus() {
+ return mAlgorithmStatus;
+ }
+
+ /**
+ * Returns the latest location algorithm suggestion. See {@link LocationAlgorithmEvent} for
+ * details.
+ */
+ @Nullable
+ public GeolocationTimeZoneSuggestion getSuggestion() {
+ return mSuggestion;
+ }
+
+ /** Returns debug information. See {@link LocationAlgorithmEvent} for details. */
+ @NonNull
+ public List<String> getDebugInfo() {
+ return mDebugInfo == null
+ ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(String... debugInfos) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.addAll(Arrays.asList(debugInfos));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationAlgorithmEvent that = (LocationAlgorithmEvent) o;
+ return mAlgorithmStatus.equals(that.mAlgorithmStatus)
+ && Objects.equals(mSuggestion, that.mSuggestion);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAlgorithmStatus, mSuggestion);
+ }
+
+ @Override
+ public String toString() {
+ return "LocationAlgorithmEvent{"
+ + "mAlgorithmStatus=" + mAlgorithmStatus
+ + ", mSuggestion=" + mSuggestion
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+
+ static LocationAlgorithmEvent parseCommandLineArg(@NonNull ShellCommand cmd) {
+ String suggestionString = null;
+ LocationTimeZoneAlgorithmStatus algorithmStatus = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--status": {
+ algorithmStatus = LocationTimeZoneAlgorithmStatus.parseCommandlineArg(
+ cmd.getNextArgRequired());
+ break;
+ }
+ case "--suggestion": {
+ suggestionString = cmd.getNextArgRequired();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (algorithmStatus == null) {
+ throw new IllegalArgumentException("Missing --status");
+ }
+
+ GeolocationTimeZoneSuggestion suggestion = null;
+ if (suggestionString != null) {
+ List<String> zoneIds = parseZoneIds(suggestionString);
+ long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
+ if (zoneIds == null) {
+ suggestion = GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ elapsedRealtimeMillis);
+ } else {
+ suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ elapsedRealtimeMillis, zoneIds);
+ }
+ }
+
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Command line injection");
+ return event;
+ }
+
+ private static List<String> parseZoneIds(String zoneIdsString) {
+ if ("UNCERTAIN".equals(zoneIdsString)) {
+ return null;
+ } else if ("EMPTY".equals(zoneIdsString)) {
+ return Collections.emptyList();
+ } else {
+ ArrayList<String> zoneIds = new ArrayList<>();
+ StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
+ while (tokenizer.hasMoreTokens()) {
+ zoneIds.add(tokenizer.nextToken());
+ }
+ return zoneIds;
+ }
+ }
+
+ static void printCommandLineOpts(@NonNull PrintWriter pw) {
+ pw.println("Location algorithm event options:");
+ pw.println(" --status {LocationTimeZoneAlgorithmStatus toString() format}");
+ pw.println(" [--suggestion {UNCERTAIN|EMPTY|<Olson ID>+}]");
+ pw.println();
+ pw.println("See " + LocationAlgorithmEvent.class.getName() + " for more information");
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index 6c36989..aad5359 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -89,7 +89,7 @@
@NonNull String deviceTimeZoneId,
@Nullable ManualTimeZoneSuggestion latestManualSuggestion,
@Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
- @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
+ @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
@@ -101,9 +101,13 @@
MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
createMetricsTimeZoneSuggestion(
tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
- MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
- createMetricsTimeZoneSuggestion(
- tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
+
+ MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = null;
+ if (latestLocationAlgorithmEvent != null) {
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+ latestCanonicalGeolocationSuggestion = createMetricsTimeZoneSuggestion(
+ tzIdOrdinalGenerator, suggestion, includeZoneIds);
+ }
return new MetricsTimeZoneDetectorState(
configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index 80cf1d6..74a518b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -59,11 +59,11 @@
boolean setManualTimeZoneForDpm(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion);
/**
- * Suggests the current time zone, determined using geolocation, to the detector. The
- * detector may ignore the signal based on system settings, whether better information is
- * available, and so on. This method may be implemented asynchronously.
+ * Handles the supplied {@link LocationAlgorithmEvent}. The detector may ignore the event based
+ * on system settings, whether better information is available, and so on. This method may be
+ * implemented asynchronously.
*/
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent);
/** Generates a state snapshot for metrics. */
@NonNull
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index dfb44df..07d0473 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -76,13 +76,14 @@
}
@Override
- public void suggestGeolocationTimeZone(
- @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
- Objects.requireNonNull(timeZoneSuggestion);
+ public void handleLocationAlgorithmEvent(
+ @NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
+ Objects.requireNonNull(locationAlgorithmEvent);
// This call can take place on the mHandler thread because there is no return value.
mHandler.post(
- () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+ locationAlgorithmEvent));
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index f415cf0..f8c1c92 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -300,12 +300,13 @@
}
/** Provided for command-line access. This is not exposed as a binder API. */
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
enforceSuggestGeolocationTimeZonePermission();
- Objects.requireNonNull(timeZoneSuggestion);
+ Objects.requireNonNull(locationAlgorithmEvent);
mHandler.post(
- () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+ locationAlgorithmEvent));
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 1b9f8e6..69274db 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -19,6 +19,7 @@
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -27,7 +28,6 @@
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE;
-import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
@@ -79,8 +79,8 @@
return runIsGeoDetectionEnabled();
case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED:
return runSetGeoDetectionEnabled();
- case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE:
- return runSuggestGeolocationTimeZone();
+ case SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT:
+ return runHandleLocationEvent();
case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE:
return runSuggestManualTimeZone();
case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE:
@@ -153,34 +153,34 @@
return mInterface.updateConfiguration(userId, configuration) ? 0 : 1;
}
- private int runSuggestGeolocationTimeZone() {
- return runSuggestTimeZone(
- () -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this),
- mInterface::suggestGeolocationTimeZone);
+ private int runHandleLocationEvent() {
+ return runSingleArgMethod(
+ () -> LocationAlgorithmEvent.parseCommandLineArg(this),
+ mInterface::handleLocationAlgorithmEvent);
}
private int runSuggestManualTimeZone() {
- return runSuggestTimeZone(
+ return runSingleArgMethod(
() -> ManualTimeZoneSuggestion.parseCommandLineArg(this),
mInterface::suggestManualTimeZone);
}
private int runSuggestTelephonyTimeZone() {
- return runSuggestTimeZone(
+ return runSingleArgMethod(
() -> TelephonyTimeZoneSuggestion.parseCommandLineArg(this),
mInterface::suggestTelephonyTimeZone);
}
- private <T> int runSuggestTimeZone(Supplier<T> suggestionParser, Consumer<T> invoker) {
+ private <T> int runSingleArgMethod(Supplier<T> argParser, Consumer<T> invoker) {
final PrintWriter pw = getOutPrintWriter();
try {
- T suggestion = suggestionParser.get();
- if (suggestion == null) {
- pw.println("Error: suggestion not specified");
+ T arg = argParser.get();
+ if (arg == null) {
+ pw.println("Error: arg not specified");
return 1;
}
- invoker.accept(suggestion);
- pw.println("Suggestion " + suggestion + " injected.");
+ invoker.accept(arg);
+ pw.println("Arg " + arg + " injected.");
return 0;
} catch (RuntimeException e) {
pw.println(e);
@@ -263,18 +263,18 @@
pw.printf(" Sets the geolocation time zone detection enabled setting.\n");
pw.printf(" %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
pw.printf(" Signals that telephony time zone detection fall back can be used if"
- + " geolocation detection is supported and enabled. This is a temporary state until"
- + " geolocation detection becomes \"certain\". To have an effect this requires that"
- + " the telephony fallback feature is supported on the device, see below for"
- + " for device_config flags.\n");
- pw.println();
- pw.printf(" %s <geolocation suggestion opts>\n",
- SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"location\" origin.\n");
+ + " geolocation detection is supported and enabled.\n)");
+ pw.printf(" This is a temporary state until geolocation detection becomes \"certain\"."
+ + "\n");
+ pw.printf(" To have an effect this requires that the telephony fallback feature is"
+ + " supported on the device, see below for device_config flags.\n");
+ pw.printf(" %s <location event opts>\n", SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT);
+ pw.printf(" Simulates an event from the location time zone detection algorithm.\n");
pw.printf(" %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"manual\" origin.\n");
+ pw.printf(" Suggests a time zone as if supplied by a user manually.\n");
pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"telephony\" origin.\n");
+ pw.printf(" Simulates a time zone suggestion from the telephony time zone detection"
+ + " algorithm.\n");
pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE);
pw.printf(" Returns the current time zone setting state.\n");
pw.printf(" %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE);
@@ -284,7 +284,7 @@
pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS);
pw.printf(" Dumps the service metrics to stdout for inspection.\n");
pw.println();
- GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
+ LocationAlgorithmEvent.printCommandLineOpts(pw);
pw.println();
ManualTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 328cf72..5768a6b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -157,10 +157,9 @@
boolean confirmTimeZone(@NonNull String timeZoneId);
/**
- * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
- * {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}.
+ * Handles an event from the location-based time zone detection algorithm.
*/
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion suggestion);
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event);
/**
* Suggests a time zone for the device using manually-entered (i.e. user sourced) information.
@@ -183,7 +182,7 @@
/**
* Tells the strategy that it can fall back to telephony detection while geolocation detection
- * remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can
+ * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can
* disable it again. See {@link TimeZoneDetectorStrategy} for details.
*/
void enableTelephonyTimeZoneFallback();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index ecf25e9..eecf0f7 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -29,9 +29,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -183,11 +187,10 @@
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
- * The latest geolocation suggestion received. If the user disabled geolocation time zone
- * detection then the latest suggestion is cleared.
+ * The latest location algorithm event received.
*/
@GuardedBy("this")
- private final ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
+ private final ReferenceWithHistory<LocationAlgorithmEvent> mLatestLocationAlgorithmEvent =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
@@ -208,6 +211,14 @@
@NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
/**
+ * A snapshot of the current detector status. A local copy is cached because it is relatively
+ * heavyweight to obtain and is used more often than it is expected to change.
+ */
+ @GuardedBy("this")
+ @NonNull
+ private TimeZoneDetectorStatus mDetectorStatus;
+
+ /**
* A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
* because it is relatively heavyweight to obtain and is used more often than it is expected to
* change. Because many operations are asynchronous, this value may be out of date but should
@@ -258,8 +269,10 @@
// Listen for config and user changes and get an initial snapshot of configuration.
StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
- mCurrentConfigurationInternal =
- mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+
+ // Initialize mCurrentConfigurationInternal and mDetectorStatus with their starting
+ // values.
+ updateCurrentConfigurationInternalIfRequired("TimeZoneDetectorStrategyImpl:");
}
}
@@ -278,6 +291,7 @@
configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
}
return new TimeZoneCapabilitiesAndConfig(
+ mDetectorStatus,
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@@ -295,28 +309,52 @@
// but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
// wouldn't see the update. So, handle the cache update and notifications here. When the
// async update listener triggers it will find everything already up to date and do nothing.
- if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
- ConfigurationInternal configurationInternal =
- mServiceConfigAccessor.getConfigurationInternal(userId);
-
- // If the configuration actually changed, update the cached copy synchronously and do
- // other necessary house-keeping / (async) listener notifications.
- if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
- mCurrentConfigurationInternal = configurationInternal;
-
- String logMsg = "updateConfiguration:"
- + " userId=" + userId
- + ", configuration=" + configuration
- + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
- + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
- logTimeZoneDebugInfo(logMsg);
-
- handleConfigurationInternalChanged(logMsg);
- }
+ if (updateSuccessful) {
+ String logMsg = "updateConfiguration:"
+ + " userId=" + userId
+ + ", configuration=" + configuration
+ + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks;
+ updateCurrentConfigurationInternalIfRequired(logMsg);
}
return updateSuccessful;
}
+ @GuardedBy("this")
+ private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) {
+ ConfigurationInternal newCurrentConfigurationInternal =
+ mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+ // mCurrentConfigurationInternal is null the first time this method is called.
+ ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal;
+
+ // If the configuration actually changed, update the cached copy synchronously and do
+ // other necessary house-keeping / (async) listener notifications.
+ if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) {
+ mCurrentConfigurationInternal = newCurrentConfigurationInternal;
+
+ logMsg += " [oldConfiguration=" + oldCurrentConfigurationInternal
+ + ", newConfiguration=" + newCurrentConfigurationInternal
+ + "]";
+ logTimeZoneDebugInfo(logMsg);
+
+ // ConfigurationInternal changes can affect the detector's status.
+ updateDetectorStatus();
+
+ // The configuration and maybe the status changed so notify listeners.
+ notifyStateChangeListenersAsynchronously();
+
+ // The configuration change may have changed available suggestions or the way
+ // suggestions are used, so re-run detection.
+ doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
+ }
+ }
+
+ @GuardedBy("this")
+ private void notifyStateChangeListenersAsynchronously() {
+ for (StateChangeListener listener : mStateChangeListeners) {
+ mStateChangeHandler.post(listener::onChange);
+ }
+ }
+
@Override
public synchronized void addChangeListener(StateChangeListener listener) {
mStateChangeListeners.add(listener);
@@ -356,33 +394,39 @@
}
@Override
- public synchronized void suggestGeolocationTimeZone(
- @NonNull GeolocationTimeZoneSuggestion suggestion) {
-
+ public synchronized void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event) {
ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
if (DBG) {
- Slog.d(LOG_TAG, "Geolocation suggestion received."
+ Slog.d(LOG_TAG, "Location algorithm event received."
+ " currentUserConfig=" + currentUserConfig
- + " newSuggestion=" + suggestion);
+ + " event=" + event);
}
- Objects.requireNonNull(suggestion);
+ Objects.requireNonNull(event);
- // Geolocation suggestions may be stored but not used during time zone detection if the
+ // Location algorithm events may be stored but not used during time zone detection if the
// configuration doesn't have geo time zone detection enabled. The caller is expected to
- // withdraw a previous suggestion (i.e. submit an "uncertain" suggestion, when geo time zone
- // detection is disabled.
+ // withdraw a previous suggestion, i.e. submit an event containing an "uncertain"
+ // suggestion, when geo time zone detection is disabled.
- // The suggestion's "effective from" time is ignored: we currently assume suggestions
- // are made in a sensible order and the most recent is always the best one to use.
- mLatestGeoLocationSuggestion.set(suggestion);
+ // We currently assume events are made in a sensible order and the most recent is always the
+ // best one to use.
+ mLatestLocationAlgorithmEvent.set(event);
+
+ // The latest location algorithm event can affect the cached detector status, so update it
+ // and notify state change listeners as needed.
+ boolean statusChanged = updateDetectorStatus();
+ if (statusChanged) {
+ notifyStateChangeListenersAsynchronously();
+ }
// Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
// will usually disable telephony fallback mode if it is currently enabled.
+ // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback.
disableTelephonyFallbackIfNeeded();
- // Now perform auto time zone detection. The new suggestion may be used to modify the
- // time zone setting.
- String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
+ // Now perform auto time zone detection. The new event may be used to modify the time zone
+ // setting.
+ String reason = "New location algorithm event received. event=" + event;
doAutoTimeZoneDetection(currentUserConfig, reason);
}
@@ -465,9 +509,9 @@
+ mTelephonyTimeZoneFallbackEnabled;
logTimeZoneDebugInfo(logMsg);
- // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
- // If there is currently a certain geolocation suggestion, then the telephony fallback
- // value needs to be considered after changing it.
+ // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact.
+ // If the latest event contains a "certain" geolocation suggestion, then the telephony
+ // fallback value needs to be considered after changing it.
// With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
// above, and the fact that geolocation suggestions should never have a time in the
// future, the following call will be a no-op, and telephony fallback will remain
@@ -507,7 +551,7 @@
mEnvironment.getDeviceTimeZone(),
getLatestManualSuggestion(),
telephonySuggestion,
- getLatestGeolocationSuggestion());
+ getLatestLocationAlgorithmEvent());
}
@Override
@@ -606,13 +650,15 @@
*/
@GuardedBy("this")
private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
- GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
- mLatestGeoLocationSuggestion.get();
- if (latestGeolocationSuggestion == null) {
+ // Terminate early if there's nothing to do.
+ LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+ if (latestLocationAlgorithmEvent == null
+ || latestLocationAlgorithmEvent.getSuggestion() == null) {
return false;
}
- List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+ List<String> zoneIds = suggestion.getZoneIds();
if (zoneIds == null) {
// This means the originator of the suggestion is uncertain about the time zone. The
// existing time zone setting must be left as it is but detection can go on looking for
@@ -645,13 +691,18 @@
}
/**
- * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo
- * suggestion is a "certain" suggestion that comes after the time when telephony fallback was
- * enabled.
+ * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest location
+ * algorithm event contains a "certain" suggestion that comes after the time when telephony
+ * fallback was enabled.
*/
@GuardedBy("this")
private void disableTelephonyFallbackIfNeeded() {
- GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get();
+ LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+ if (latestLocationAlgorithmEvent == null) {
+ return;
+ }
+
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null;
if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) {
// This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from
@@ -809,33 +860,27 @@
* Handles a configuration change notification.
*/
private synchronized void handleConfigurationInternalMaybeChanged() {
- ConfigurationInternal currentUserConfig =
- mServiceConfigAccessor.getCurrentUserConfigurationInternal();
-
- // The configuration may not actually have changed so check before doing anything.
- if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
- String logMsg = "handleConfigurationInternalMaybeChanged:"
- + " oldConfiguration=" + mCurrentConfigurationInternal
- + ", newConfiguration=" + currentUserConfig;
- logTimeZoneDebugInfo(logMsg);
-
- mCurrentConfigurationInternal = currentUserConfig;
-
- handleConfigurationInternalChanged(logMsg);
- }
+ String logMsg = "handleConfigurationInternalMaybeChanged:";
+ updateCurrentConfigurationInternalIfRequired(logMsg);
}
- /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+ /**
+ * Called whenever the information that contributes to {@link #mDetectorStatus} could have
+ * changed. Updates the cached status snapshot if required.
+ *
+ * @return true if the status had changed and has been updated
+ */
@GuardedBy("this")
- private void handleConfigurationInternalChanged(@NonNull String logMsg) {
- // Notify change listeners asynchronously.
- for (StateChangeListener listener : mStateChangeListeners) {
- mStateChangeHandler.post(listener::onChange);
+ private boolean updateDetectorStatus() {
+ TimeZoneDetectorStatus newDetectorStatus = createTimeZoneDetectorStatus(
+ mCurrentConfigurationInternal, mLatestLocationAlgorithmEvent.get());
+ // mDetectorStatus is null the first time this method is called.
+ TimeZoneDetectorStatus oldDetectorStatus = mDetectorStatus;
+ boolean statusChanged = !newDetectorStatus.equals(oldDetectorStatus);
+ if (statusChanged) {
+ mDetectorStatus = newDetectorStatus;
}
-
- // The configuration change may have changed available suggestions or the way
- // suggestions are used, so re-run detection.
- doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
+ return statusChanged;
}
/**
@@ -847,6 +892,7 @@
ipw.increaseIndent(); // level 1
ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
+ ipw.println("mDetectorStatus=" + mDetectorStatus);
final boolean bypassUserPolicyChecks = false;
ipw.println("[Capabilities="
+ mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
@@ -870,9 +916,9 @@
mLatestManualSuggestion.dump(ipw);
ipw.decreaseIndent(); // level 2
- ipw.println("Geolocation suggestion history:");
+ ipw.println("Location algorithm event history:");
ipw.increaseIndent(); // level 2
- mLatestGeoLocationSuggestion.dump(ipw);
+ mLatestLocationAlgorithmEvent.dump(ipw);
ipw.decreaseIndent(); // level 2
ipw.println("Telephony suggestion history:");
@@ -886,6 +932,7 @@
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
+ @Nullable
public synchronized ManualTimeZoneSuggestion getLatestManualSuggestion() {
return mLatestManualSuggestion.get();
}
@@ -894,6 +941,7 @@
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
+ @Nullable
public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
int slotIndex) {
return mTelephonySuggestionsBySlotIndex.get(slotIndex);
@@ -903,8 +951,9 @@
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
- public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
- return mLatestGeoLocationSuggestion.get();
+ @Nullable
+ public synchronized LocationAlgorithmEvent getLatestLocationAlgorithmEvent() {
+ return mLatestLocationAlgorithmEvent.get();
}
@VisibleForTesting
@@ -917,6 +966,11 @@
return mCurrentConfigurationInternal;
}
+ @VisibleForTesting
+ public synchronized TimeZoneDetectorStatus getCachedDetectorStatusForTests() {
+ return mDetectorStatus;
+ }
+
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
@@ -970,4 +1024,42 @@
private static String formatDebugString(TimestampedValue<?> value) {
return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis());
}
+
+ @NonNull
+ private static TimeZoneDetectorStatus createTimeZoneDetectorStatus(
+ @NonNull ConfigurationInternal currentConfigurationInternal,
+ @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
+
+ int detectorStatus;
+ if (!currentConfigurationInternal.isAutoDetectionSupported()) {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_SUPPORTED;
+ } else if (currentConfigurationInternal.getAutoDetectionEnabledBehavior()) {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+ } else {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+ }
+
+ TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+ createTelephonyAlgorithmStatus(currentConfigurationInternal);
+
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ latestLocationAlgorithmEvent == null ? LocationTimeZoneAlgorithmStatus.UNKNOWN
+ : latestLocationAlgorithmEvent.getAlgorithmStatus();
+
+ return new TimeZoneDetectorStatus(
+ detectorStatus, telephonyAlgorithmStatus, locationAlgorithmStatus);
+ }
+
+ @NonNull
+ private static TelephonyTimeZoneAlgorithmStatus createTelephonyAlgorithmStatus(
+ @NonNull ConfigurationInternal currentConfigurationInternal) {
+ int algorithmStatus;
+ if (!currentConfigurationInternal.isTelephonyDetectionSupported()) {
+ algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ } else {
+ // The telephony detector is passive, so we treat it as "running".
+ algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+ }
+ return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index a1de294..71aa10d 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -53,7 +53,7 @@
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mProxy.initialize(new LocationTimeZoneProviderProxy.Listener() {
@Override
public void onReportTimeZoneProviderEvent(
@@ -71,6 +71,7 @@
handleTemporaryFailure("onProviderUnbound()");
}
});
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
new file mode 100644
index 0000000..5d6184e
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
@@ -0,0 +1,86 @@
+/*
+ * 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.server.timezonedetector.location;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.time.Duration;
+
+/**
+ * A {@link LocationTimeZoneProvider} that provides minimal responses needed to operate correctly
+ * when there is no "real" provider configured / enabled. This is used when the platform supports
+ * more providers than are needed for an Android deployment.
+ *
+ * <p>That is, the {@link LocationTimeZoneProviderController} supports a primary and a secondary
+ * {@link LocationTimeZoneProvider}, but if only a primary is configured, the secondary provider
+ * config will marked as "disabled" and the {@link LocationTimeZoneProvider} implementation will use
+ * {@link DisabledLocationTimeZoneProvider}. The {@link DisabledLocationTimeZoneProvider} fails
+ * initialization and immediately moves to a "permanent failure" state, which ensures the {@link
+ * LocationTimeZoneProviderController} correctly categorizes it and won't attempt to use it.
+ */
+class DisabledLocationTimeZoneProvider extends LocationTimeZoneProvider {
+
+ DisabledLocationTimeZoneProvider(
+ @NonNull ProviderMetricsLogger providerMetricsLogger,
+ @NonNull ThreadingDomain threadingDomain,
+ @NonNull String providerName,
+ boolean recordStateChanges) {
+ super(providerMetricsLogger, threadingDomain, providerName, x -> x, recordStateChanges);
+ }
+
+ @Override
+ boolean onInitialize() {
+ // Fail initialization, preventing further use.
+ return false;
+ }
+
+ @Override
+ void onDestroy() {
+ }
+
+ @Override
+ void onStartUpdates(@NonNull Duration initializationTimeout,
+ @NonNull Duration eventFilteringAgeThreshold) {
+ throw new UnsupportedOperationException("Provider is disabled");
+ }
+
+ @Override
+ void onStopUpdates() {
+ throw new UnsupportedOperationException("Provider is disabled");
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("{DisabledLocationTimeZoneProvider}");
+ ipw.println("mProviderName=" + mProviderName);
+ ipw.println("mCurrentState=" + mCurrentState);
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mSharedLock) {
+ return "DisabledLocationTimeZoneProvider{"
+ + "mProviderName=" + mProviderName
+ + ", mCurrentState=" + mCurrentState
+ + '}';
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 36ab111d..8d98544 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -447,11 +447,18 @@
@NonNull
LocationTimeZoneProvider createProvider() {
- LocationTimeZoneProviderProxy proxy = createProxy();
ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex);
- return new BinderLocationTimeZoneProvider(
- providerMetricsLogger, mThreadingDomain, mName, proxy,
- mServiceConfigAccessor.getRecordStateChangesForTests());
+
+ String mode = getMode();
+ if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
+ return new DisabledLocationTimeZoneProvider(providerMetricsLogger, mThreadingDomain,
+ mName, mServiceConfigAccessor.getRecordStateChangesForTests());
+ } else {
+ LocationTimeZoneProviderProxy proxy = createBinderProxy();
+ return new BinderLocationTimeZoneProvider(
+ providerMetricsLogger, mThreadingDomain, mName, proxy,
+ mServiceConfigAccessor.getRecordStateChangesForTests());
+ }
}
@Override
@@ -460,17 +467,6 @@
ipw.printf("getPackageName()=%s\n", getPackageName());
}
- @NonNull
- private LocationTimeZoneProviderProxy createProxy() {
- String mode = getMode();
- if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
- return new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
- } else {
- // mode == PROVIDER_MODE_OVERRIDE_ENABLED (or unknown).
- return createRealProxy();
- }
- }
-
/** Returns the mode of the provider (enabled/disabled). */
@NonNull
private String getMode() {
@@ -482,7 +478,7 @@
}
@NonNull
- private RealLocationTimeZoneProviderProxy createRealProxy() {
+ private RealLocationTimeZoneProviderProxy createBinderProxy() {
String providerServiceAction = mServiceAction;
boolean isTestProvider = isTestProvider();
String providerPackageName = getPackageName();
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
index 1f752f4..e90a1fe 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
@@ -32,14 +32,14 @@
final class LocationTimeZoneManagerServiceState {
private final @State String mControllerState;
- @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion;
+ @Nullable private final LocationAlgorithmEvent mLastEvent;
@NonNull private final List<@State String> mControllerStates;
@NonNull private final List<ProviderState> mPrimaryProviderStates;
@NonNull private final List<ProviderState> mSecondaryProviderStates;
LocationTimeZoneManagerServiceState(@NonNull Builder builder) {
mControllerState = builder.mControllerState;
- mLastSuggestion = builder.mLastSuggestion;
+ mLastEvent = builder.mLastEvent;
mControllerStates = Objects.requireNonNull(builder.mControllerStates);
mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates);
mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates);
@@ -50,8 +50,8 @@
}
@Nullable
- public GeolocationTimeZoneSuggestion getLastSuggestion() {
- return mLastSuggestion;
+ public LocationAlgorithmEvent getLastEvent() {
+ return mLastEvent;
}
@NonNull
@@ -73,7 +73,7 @@
public String toString() {
return "LocationTimeZoneManagerServiceState{"
+ "mControllerState=" + mControllerState
- + ", mLastSuggestion=" + mLastSuggestion
+ + ", mLastEvent=" + mLastEvent
+ ", mControllerStates=" + mControllerStates
+ ", mPrimaryProviderStates=" + mPrimaryProviderStates
+ ", mSecondaryProviderStates=" + mSecondaryProviderStates
@@ -83,7 +83,7 @@
static final class Builder {
private @State String mControllerState;
- private GeolocationTimeZoneSuggestion mLastSuggestion;
+ private LocationAlgorithmEvent mLastEvent;
private List<@State String> mControllerStates;
private List<ProviderState> mPrimaryProviderStates;
private List<ProviderState> mSecondaryProviderStates;
@@ -95,8 +95,8 @@
}
@NonNull
- Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) {
- mLastSuggestion = Objects.requireNonNull(lastSuggestion);
+ Builder setLastEvent(@NonNull LocationAlgorithmEvent lastEvent) {
+ mLastEvent = Objects.requireNonNull(lastEvent);
return this;
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
index 60bbea7..cefd0b5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
@@ -15,6 +15,10 @@
*/
package com.android.server.timezonedetector.location;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
import static android.app.time.LocationTimeZoneManager.NULL_PACKAGE_NAME_TOKEN;
import static android.app.time.LocationTimeZoneManager.SERVICE_NAME;
@@ -51,9 +55,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
import android.app.time.GeolocationTimeZoneSuggestionProto;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatusProto;
import android.app.time.LocationTimeZoneManagerProto;
import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.LocationTimeZoneProviderEventProto;
+import android.app.time.TimeZoneDetectorProto;
import android.app.time.TimeZoneProviderStateProto;
import android.app.timezonedetector.TimeZoneDetector;
import android.os.ShellCommand;
@@ -62,6 +71,7 @@
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
@@ -239,19 +249,39 @@
outputStream = new DualDumpOutputStream(
new IndentingPrintWriter(getOutPrintWriter(), " "));
}
- if (state.getLastSuggestion() != null) {
- GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion();
- long lastSuggestionToken = outputStream.start(
- "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION);
- for (String zoneId : lastSuggestion.getZoneIds()) {
- outputStream.write(
- "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+
+ if (state.getLastEvent() != null) {
+ LocationAlgorithmEvent lastEvent = state.getLastEvent();
+ long lastEventToken = outputStream.start(
+ "last_event", LocationTimeZoneManagerServiceStateProto.LAST_EVENT);
+
+ // lastEvent.algorithmStatus
+ LocationTimeZoneAlgorithmStatus algorithmStatus = lastEvent.getAlgorithmStatus();
+ long algorithmStatusToken = outputStream.start(
+ "algorithm_status", LocationTimeZoneProviderEventProto.ALGORITHM_STATUS);
+ outputStream.write("status", LocationTimeZoneAlgorithmStatusProto.STATUS,
+ convertDetectionAlgorithmStatusToEnumToProtoEnum(algorithmStatus.getStatus()));
+ outputStream.end(algorithmStatusToken);
+
+ // lastEvent.suggestion
+ if (lastEvent.getSuggestion() != null) {
+ long suggestionToken = outputStream.start(
+ "suggestion", LocationTimeZoneProviderEventProto.SUGGESTION);
+ GeolocationTimeZoneSuggestion lastSuggestion = lastEvent.getSuggestion();
+ for (String zoneId : lastSuggestion.getZoneIds()) {
+ outputStream.write(
+ "zone_ids", GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+ }
+ outputStream.end(suggestionToken);
}
- for (String debugInfo : lastSuggestion.getDebugInfo()) {
+
+ // lastEvent.debugInfo
+ for (String debugInfo : lastEvent.getDebugInfo()) {
outputStream.write(
- "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo);
+ "debug_info", LocationTimeZoneProviderEventProto.DEBUG_INFO, debugInfo);
}
- outputStream.end(lastSuggestionToken);
+
+ outputStream.end(lastEventToken);
}
writeControllerStates(outputStream, state.getControllerStates());
@@ -330,6 +360,22 @@
}
}
+ private static int convertDetectionAlgorithmStatusToEnumToProtoEnum(
+ @DetectionAlgorithmStatus int statusEnum) {
+ switch (statusEnum) {
+ case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ case DETECTION_ALGORITHM_STATUS_RUNNING:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException("Unknown statusEnum=" + statusEnum);
+ }
+ }
+
private void reportError(@NonNull Throwable e) {
PrintWriter errPrintWriter = getErrPrintWriter();
errPrintWriter.println("Error: ");
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index 90540b0..ba7c328 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector.location;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
@@ -33,9 +37,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
import android.os.Handler;
import android.os.SystemClock;
import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -294,6 +300,40 @@
|| stateEnum == PROVIDER_STATE_DESTROYED;
}
+ /**
+ * Maps the internal state enum value to one of the status values exposed to the layers
+ * above.
+ */
+ public @ProviderStatus int getProviderStatus() {
+ switch (stateEnum) {
+ case PROVIDER_STATE_STARTED_INITIALIZING:
+ return PROVIDER_STATUS_NOT_READY;
+ case PROVIDER_STATE_STARTED_CERTAIN:
+ return PROVIDER_STATUS_IS_CERTAIN;
+ case PROVIDER_STATE_STARTED_UNCERTAIN:
+ return PROVIDER_STATUS_IS_UNCERTAIN;
+ case PROVIDER_STATE_PERM_FAILED:
+ // Perm failed means the providers wasn't configured, configured properly,
+ // or has removed itself for other reasons, e.g. turned-down server.
+ return PROVIDER_STATUS_NOT_PRESENT;
+ case PROVIDER_STATE_STOPPED:
+ case PROVIDER_STATE_DESTROYED:
+ // This is a "safe" default that best describes a provider that isn't in one of
+ // the more obviously mapped states.
+ return PROVIDER_STATUS_NOT_READY;
+ case PROVIDER_STATE_UNKNOWN:
+ default:
+ throw new IllegalStateException(
+ "Unknown state enum:" + prettyPrintStateEnum(stateEnum));
+ }
+ }
+
+ /** Returns the status reported by the provider, if available. */
+ @Nullable
+ TimeZoneProviderStatus getReportedStatus() {
+ return event == null ? null : event.getTimeZoneProviderStatus();
+ }
+
@Override
public String toString() {
// this.provider is omitted deliberately to avoid recursion, since the provider holds
@@ -408,13 +448,21 @@
currentState = currentState.newState(PROVIDER_STATE_STOPPED, null, null, "initialize");
setCurrentState(currentState, false);
+ boolean initializationSuccess;
+ String initializationFailureReason;
// Guard against uncaught exceptions due to initialization problems.
try {
- onInitialize();
+ initializationSuccess = onInitialize();
+ initializationFailureReason = "onInitialize() returned false";
} catch (RuntimeException e) {
- warnLog("Unable to initialize the provider", e);
+ warnLog("Unable to initialize the provider due to exception", e);
+ initializationSuccess = false;
+ initializationFailureReason = "onInitialize() threw exception:" + e.getMessage();
+ }
+
+ if (!initializationSuccess) {
currentState = currentState.newState(PROVIDER_STATE_PERM_FAILED, null, null,
- "Failed to initialize: " + e.getMessage());
+ "Failed to initialize: " + initializationFailureReason);
setCurrentState(currentState, true);
}
}
@@ -422,9 +470,12 @@
/**
* Implemented by subclasses to do work during {@link #initialize}.
+ *
+ * @return returns {@code true} on success, {@code false} if the provider should be considered
+ * "permanently failed" / disabled
*/
@GuardedBy("mSharedLock")
- abstract void onInitialize();
+ abstract boolean onInitialize();
/**
* Destroys the provider. Called after the provider is stopped. This instance will not be called
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index a9b9884..ed7ea00 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -35,6 +35,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -44,6 +48,7 @@
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.ReferenceWithHistory;
import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
@@ -83,8 +88,7 @@
* <p>All incoming calls except for {@link
* LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be
* made on the {@link android.os.Handler} thread of the {@link ThreadingDomain} passed to {@link
- * #LocationTimeZoneProviderController(ThreadingDomain, LocationTimeZoneProvider,
- * LocationTimeZoneProvider)}.
+ * #LocationTimeZoneProviderController}.
*
* <p>Provider / controller integration notes:
*
@@ -172,10 +176,10 @@
@GuardedBy("mSharedLock")
private final ReferenceWithHistory<@State String> mState = new ReferenceWithHistory<>(10);
- /** Contains the last suggestion actually made, if there is one. */
+ /** Contains the last event reported, if there is one. */
@GuardedBy("mSharedLock")
@Nullable
- private GeolocationTimeZoneSuggestion mLastSuggestion;
+ private LocationAlgorithmEvent mLastEvent;
LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain,
@NonNull MetricsLogger metricsLogger,
@@ -213,7 +217,7 @@
setState(STATE_PROVIDERS_INITIALIZING);
mPrimaryProvider.initialize(providerListener);
mSecondaryProvider.initialize(providerListener);
- setState(STATE_STOPPED);
+ setStateAndReportStatusOnlyEvent(STATE_STOPPED, "initialize()");
alterProvidersStartedStateIfRequired(
null /* oldConfiguration */, mCurrentUserConfiguration);
@@ -273,13 +277,51 @@
// Enter destroyed state.
mPrimaryProvider.destroy();
mSecondaryProvider.destroy();
- setState(STATE_DESTROYED);
+ setStateAndReportStatusOnlyEvent(STATE_DESTROYED, "destroy()");
}
}
/**
- * Updates {@link #mState} if needed, and performs all the record-keeping / callbacks associated
- * with state changes.
+ * Sets the state and reports an event containing the algorithm status and a {@code null}
+ * suggestion.
+ */
+ @GuardedBy("mSharedLock")
+ private void setStateAndReportStatusOnlyEvent(@State String state, @NonNull String reason) {
+ setState(state);
+
+ final GeolocationTimeZoneSuggestion suggestion = null;
+ LocationAlgorithmEvent event =
+ new LocationAlgorithmEvent(generateCurrentAlgorithmStatus(), suggestion);
+ event.addDebugInfo(reason);
+ reportEvent(event);
+ }
+
+ /**
+ * Reports an event containing the algorithm status and the supplied suggestion.
+ */
+ @GuardedBy("mSharedLock")
+ private void reportSuggestionEvent(
+ @NonNull GeolocationTimeZoneSuggestion suggestion, @NonNull String reason) {
+ LocationTimeZoneAlgorithmStatus algorithmStatus = generateCurrentAlgorithmStatus();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ algorithmStatus, suggestion);
+ event.addDebugInfo(reason);
+ reportEvent(event);
+ }
+
+ /**
+ * Sends an event immediately. This method updates {@link #mLastEvent}.
+ */
+ @GuardedBy("mSharedLock")
+ private void reportEvent(@NonNull LocationAlgorithmEvent event) {
+ debugLog("makeSuggestion: suggestion=" + event);
+ mCallback.sendEvent(event);
+ mLastEvent = event;
+ }
+
+ /**
+ * Updates the state if needed. This includes setting {@link #mState} and performing all the
+ * record-keeping / callbacks associated with state changes.
*/
@GuardedBy("mSharedLock")
private void setState(@State String state) {
@@ -300,17 +342,7 @@
// By definition, if both providers are stopped, the controller is uncertain.
cancelUncertaintyTimeout();
- // If a previous "certain" suggestion has been made, then a new "uncertain"
- // suggestion must now be made to indicate the controller {does not / no longer has}
- // an opinion and will not be sending further updates (until at least the providers are
- // re-started).
- if (Objects.equals(mState.get(), STATE_CERTAIN)) {
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Withdraw previous suggestion, providers are stopping: " + reason);
- makeSuggestion(suggestion, STATE_UNCERTAIN);
- }
- setState(STATE_STOPPED);
+ setStateAndReportStatusOnlyEvent(STATE_STOPPED, "Providers stopped: " + reason);
}
@GuardedBy("mSharedLock")
@@ -381,7 +413,7 @@
// timeout started when the primary entered {started uncertain} should be cancelled.
if (newIsGeoDetectionExecutionEnabled) {
- setState(STATE_INITIALIZING);
+ setStateAndReportStatusOnlyEvent(STATE_INITIALIZING, "initializing()");
// Try to start the primary provider.
tryStartProvider(mPrimaryProvider, newConfiguration);
@@ -397,13 +429,11 @@
ProviderState newSecondaryState = mSecondaryProvider.getCurrentState();
if (!newSecondaryState.isStarted()) {
// If both providers are {perm failed} then the controller immediately
- // reports uncertain.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Providers are failed:"
- + " primary=" + mPrimaryProvider.getCurrentState()
- + " secondary=" + mPrimaryProvider.getCurrentState());
- makeSuggestion(suggestion, STATE_FAILED);
+ // reports the failure.
+ String reason = "Providers are failed:"
+ + " primary=" + mPrimaryProvider.getCurrentState()
+ + " secondary=" + mPrimaryProvider.getCurrentState();
+ setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
}
}
} else {
@@ -537,12 +567,10 @@
// If both providers are now terminated, then a suggestion must be sent informing the
// time zone detector that there are no further updates coming in the future.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Both providers are terminated:"
- + " primary=" + primaryCurrentState.provider
- + ", secondary=" + secondaryCurrentState.provider);
- makeSuggestion(suggestion, STATE_FAILED);
+ String reason = "Both providers are terminated:"
+ + " primary=" + primaryCurrentState.provider
+ + ", secondary=" + secondaryCurrentState.provider;
+ setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
}
}
@@ -615,6 +643,9 @@
TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
+ // Set the current state so it is correct when the suggestion event is created.
+ setState(STATE_CERTAIN);
+
// For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
// suggestion (which indicates the time when the provider detected the location used to
// establish the time zone).
@@ -623,15 +654,13 @@
// this would hinder the ability for the time_zone_detector to judge which suggestions are
// based on newer information when comparing suggestions between different sources.
long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
- GeolocationTimeZoneSuggestion geoSuggestion =
+ GeolocationTimeZoneSuggestion suggestion =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
-
- String debugInfo = "Event received provider=" + provider
+ String debugInfo = "Provider event received: provider=" + provider
+ ", providerEvent=" + providerEvent
+ ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
- geoSuggestion.addDebugInfo(debugInfo);
- makeSuggestion(geoSuggestion, STATE_CERTAIN);
+ reportSuggestionEvent(suggestion, debugInfo);
}
@Override
@@ -647,7 +676,7 @@
+ mEnvironment.getProviderInitializationTimeoutFuzz());
ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
ipw.println("mState=" + mState.get());
- ipw.println("mLastSuggestion=" + mLastSuggestion);
+ ipw.println("mLastEvent=" + mLastEvent);
ipw.println("State history:");
ipw.increaseIndent(); // level 2
@@ -668,19 +697,6 @@
}
}
- /**
- * Sends an immediate suggestion and enters a new state if needed. This method updates
- * mLastSuggestion and changes mStateEnum / reports the new state for metrics.
- */
- @GuardedBy("mSharedLock")
- private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion,
- @State String newState) {
- debugLog("makeSuggestion: suggestion=" + suggestion);
- mCallback.suggest(suggestion);
- mLastSuggestion = suggestion;
- setState(newState);
- }
-
/** Clears the uncertainty timeout. */
@GuardedBy("mSharedLock")
private void cancelUncertaintyTimeout() {
@@ -688,18 +704,16 @@
}
/**
- * Called when a provider has become "uncertain" about the time zone.
+ * Called when a provider has reported it is "uncertain" about the time zone.
*
* <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
* this enables the most flexibility for the controller to start other providers when there are
- * multiple ones available. The controller is therefore responsible for deciding when to make a
- * "uncertain" suggestion to the downstream time zone detector.
+ * multiple ones available. The controller is therefore responsible for deciding when to pass
+ * the "uncertain" suggestion to the downstream time zone detector.
*
* <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be
* triggered later if nothing else preempts it. It can be preempted if the provider becomes
- * certain (or does anything else that calls {@link
- * #makeSuggestion(GeolocationTimeZoneSuggestion, String)}) within {@link
- * Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+ * certain within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
* "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events
* within the uncertainty delay period, those events are effectively ignored (i.e. the timeout
* is not reset each time).
@@ -741,6 +755,8 @@
synchronized (mSharedLock) {
long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
+ setState(STATE_UNCERTAIN);
+
// For the effectiveFromElapsedMillis suggestion property, use the
// uncertaintyStartedElapsedMillis. This is the time when the provider first reported
// uncertainty, i.e. before the uncertainty timeout.
@@ -749,30 +765,65 @@
// the location_time_zone_manager finally confirms that the time zone was uncertain,
// but the suggestion property allows the information to be back-dated, which should
// help when comparing suggestions from different sources.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- uncertaintyStartedElapsedMillis,
- "Uncertainty timeout triggered for " + provider.getName() + ":"
- + " primary=" + mPrimaryProvider
- + ", secondary=" + mSecondaryProvider
- + ", uncertaintyStarted="
- + Duration.ofMillis(uncertaintyStartedElapsedMillis)
- + ", afterUncertaintyTimeout="
- + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
- + ", uncertaintyDelay=" + uncertaintyDelay
- );
- makeSuggestion(suggestion, STATE_UNCERTAIN);
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ uncertaintyStartedElapsedMillis);
+ String debugInfo = "Uncertainty timeout triggered for " + provider.getName() + ":"
+ + " primary=" + mPrimaryProvider
+ + ", secondary=" + mSecondaryProvider
+ + ", uncertaintyStarted="
+ + Duration.ofMillis(uncertaintyStartedElapsedMillis)
+ + ", afterUncertaintyTimeout="
+ + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
+ + ", uncertaintyDelay=" + uncertaintyDelay;
+ reportSuggestionEvent(suggestion, debugInfo);
}
}
+ @GuardedBy("mSharedLock")
@NonNull
- private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @NonNull String reason) {
- GeolocationTimeZoneSuggestion suggestion =
- GeolocationTimeZoneSuggestion.createUncertainSuggestion(
- effectiveFromElapsedMillis);
- suggestion.addDebugInfo(reason);
- return suggestion;
+ private LocationTimeZoneAlgorithmStatus generateCurrentAlgorithmStatus() {
+ @State String controllerState = mState.get();
+ ProviderState primaryProviderState = mPrimaryProvider.getCurrentState();
+ ProviderState secondaryProviderState = mSecondaryProvider.getCurrentState();
+ return createAlgorithmStatus(controllerState, primaryProviderState, secondaryProviderState);
+ }
+
+ @NonNull
+ private static LocationTimeZoneAlgorithmStatus createAlgorithmStatus(
+ @NonNull @State String controllerState,
+ @NonNull ProviderState primaryProviderState,
+ @NonNull ProviderState secondaryProviderState) {
+
+ @DetectionAlgorithmStatus int algorithmStatus =
+ mapControllerStateToDetectionAlgorithmStatus(controllerState);
+ @ProviderStatus int primaryProviderStatus = primaryProviderState.getProviderStatus();
+ @ProviderStatus int secondaryProviderStatus = secondaryProviderState.getProviderStatus();
+
+ // Neither provider is running. The algorithm is not running.
+ return new LocationTimeZoneAlgorithmStatus(algorithmStatus,
+ primaryProviderStatus, primaryProviderState.getReportedStatus(),
+ secondaryProviderStatus, secondaryProviderState.getReportedStatus());
+ }
+
+ /**
+ * Maps the internal state enum value to one of the status values exposed to the layers above.
+ */
+ private static @DetectionAlgorithmStatus int mapControllerStateToDetectionAlgorithmStatus(
+ @NonNull @State String controllerState) {
+ switch (controllerState) {
+ case STATE_INITIALIZING:
+ case STATE_PROVIDERS_INITIALIZING:
+ case STATE_CERTAIN:
+ case STATE_UNCERTAIN:
+ return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+ case STATE_STOPPED:
+ case STATE_DESTROYED:
+ case STATE_FAILED:
+ case STATE_UNKNOWN:
+ default:
+ return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ }
}
/**
@@ -798,8 +849,8 @@
synchronized (mSharedLock) {
LocationTimeZoneManagerServiceState.Builder builder =
new LocationTimeZoneManagerServiceState.Builder();
- if (mLastSuggestion != null) {
- builder.setLastSuggestion(mLastSuggestion);
+ if (mLastEvent != null) {
+ builder.setLastEvent(mLastEvent);
}
builder.setControllerState(mState.get())
.setStateChanges(mRecordedStates)
@@ -867,17 +918,15 @@
abstract static class Callback {
@NonNull protected final ThreadingDomain mThreadingDomain;
- @NonNull protected final Object mSharedLock;
Callback(@NonNull ThreadingDomain threadingDomain) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
- mSharedLock = threadingDomain.getLockObject();
}
/**
* Suggests the latest time zone state for the device.
*/
- abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion);
+ abstract void sendEvent(@NonNull LocationAlgorithmEvent event);
}
/**
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
index 0c751aa..7eb7e01 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import com.android.server.LocalServices;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
/**
@@ -34,11 +34,11 @@
}
@Override
- void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ void sendEvent(@NonNull LocationAlgorithmEvent event) {
mThreadingDomain.assertCurrentThread();
TimeZoneDetectorInternal timeZoneDetector =
LocalServices.getService(TimeZoneDetectorInternal.class);
- timeZoneDetector.suggestGeolocationTimeZone(suggestion);
+ timeZoneDetector.handleLocationAlgorithmEvent(event);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
deleted file mode 100644
index 9cb1813..0000000
--- a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.timezonedetector.location;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.SystemClock;
-import android.service.timezone.TimeZoneProviderEvent;
-import android.util.IndentingPrintWriter;
-
-/**
- * A {@link LocationTimeZoneProviderProxy} that provides minimal responses needed for the {@link
- * BinderLocationTimeZoneProvider} to operate correctly when there is no "real" provider
- * configured / enabled. This can be used during development / testing, or in a production build
- * when the platform supports more providers than are needed for an Android deployment.
- *
- * <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
- * and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
- * config will be left null and the {@link LocationTimeZoneProviderProxy} implementation will be
- * defaulted to a {@link NullLocationTimeZoneProviderProxy}. The {@link
- * NullLocationTimeZoneProviderProxy} sends a "permanent failure" event immediately after being
- * started for the first time, which ensures the {@link LocationTimeZoneProviderController} won't
- * expect any further {@link TimeZoneProviderEvent}s to come from it, and won't attempt to use it
- * again.
- */
-class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
-
- /** Creates the instance. */
- NullLocationTimeZoneProviderProxy(
- @NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
- super(context, threadingDomain);
- }
-
- @Override
- void onInitialize() {
- // No-op
- }
-
- @Override
- void onDestroy() {
- // No-op
- }
-
- @Override
- void setRequest(@NonNull TimeZoneProviderRequest request) {
- if (request.sendUpdates()) {
- TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
- SystemClock.elapsedRealtime(), "Provider is disabled");
- handleTimeZoneProviderEvent(event);
- }
- }
-
- @Override
- public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
- synchronized (mSharedLock) {
- ipw.println("{NullLocationTimeZoneProviderProxy}");
- }
- }
-}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index 8a6f927..aa2b74e 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -58,7 +58,7 @@
if (hasInvalidZones(event)) {
TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder(
event.getTimeZoneProviderStatus())
- .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
.build();
return TimeZoneProviderEvent.createUncertainEvent(
event.getCreationElapsedMillis(), providerStatus);
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index f52f0b7..2c8fd96 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1066,7 +1066,28 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
+ }
+ @Override
+ public void notifyRecordingStopped(IBinder sessionToken, String recordingId, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyRecordingStopped");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyRecordingStopped(recordingId);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyRecordingStopped", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
@@ -2253,6 +2274,23 @@
}
@Override
+ public void onRequestStopRecording(String recordingId) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestStopRecording");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestStopRecording(recordingId, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestStopRecording", e);
+ }
+ }
+ }
+
+ @Override
public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
synchronized (mLock) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7dc4f97..c875f4a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1157,6 +1157,8 @@
Slog.w(TAG, "WallpaperService is not connected yet");
return;
}
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
null /* options */);
@@ -1173,6 +1175,7 @@
false /* fromUser */, wallpaper, null /* reply */);
}
}
+ t.traceEnd();
}
void disconnectLocked() {
@@ -1322,6 +1325,8 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.onServiceConnected-" + name);
synchronized (mLock) {
if (mWallpaper.connection == this) {
mService = IWallpaperService.Stub.asInterface(service);
@@ -1338,6 +1343,7 @@
mContext.getMainThreadHandler().removeCallbacks(mDisconnectRunnable);
}
}
+ t.traceEnd();
}
@Override
@@ -1545,6 +1551,8 @@
public void engineShown(IWallpaperEngine engine) {
synchronized (mLock) {
if (mReply != null) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.mReply.sendResult");
final long ident = Binder.clearCallingIdentity();
try {
mReply.sendResult(null);
@@ -1553,6 +1561,7 @@
} finally {
Binder.restoreCallingIdentity(ident);
}
+ t.traceEnd();
mReply = null;
}
}
@@ -1888,12 +1897,9 @@
}
}
- private static final HashMap<Integer, String> sWallpaperType = new HashMap<Integer, String>() {
- {
- put(FLAG_SYSTEM, RECORD_FILE);
- put(FLAG_LOCK, RECORD_LOCK_FILE);
- }
- };
+ private static final Map<Integer, String> sWallpaperType = Map.of(
+ FLAG_SYSTEM, RECORD_FILE,
+ FLAG_LOCK, RECORD_LOCK_FILE);
private void errorCheck(int userID) {
sWallpaperType.forEach((type, filename) -> {
@@ -3058,6 +3064,8 @@
return true;
}
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.bindWallpaperComponentLocked-" + componentName);
try {
if (componentName == null) {
componentName = mDefaultWallpaperComponent;
@@ -3190,6 +3198,8 @@
}
Slog.w(TAG, msg);
return false;
+ } finally {
+ t.traceEnd();
}
return true;
}
@@ -3234,7 +3244,10 @@
}
private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.attachServiceLocked");
conn.forEachDisplayConnector(connector-> connector.connectLocked(conn, wallpaper));
+ t.traceEnd();
}
private void notifyCallbacksLocked(WallpaperData wallpaper) {
@@ -3360,6 +3373,8 @@
}
void saveSettingsLocked(int userId) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.saveSettingsLocked-" + userId);
JournaledFile journal = makeJournaledFile(userId);
FileOutputStream fstream = null;
try {
@@ -3388,6 +3403,7 @@
IoUtils.closeQuietly(fstream);
journal.rollback();
}
+ t.traceEnd();
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index d0c381e..21b241a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -56,8 +56,11 @@
private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35;
// To avoid the surface flinger callbacks always comes within in 2 frames, then no windows
// are reported to the A11y framework, and the animation duration time is 500ms, so setting
- // this value as the max timeout value to force computing changed windows.
- private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500;
+ // this value as the max timeout value to force computing changed windows. However, since
+ // UiAutomator waits 500ms to determine that things are idle. Since we aren't actually idle,
+ // we need to reduce the timeout here a little so that we can deliver an updated state before
+ // UiAutomator reports idle based-on stale information.
+ private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 450;
private static final float[] sTempFloats = new float[9];
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 59f37c2..7157293 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -86,6 +86,7 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.pm.KnownPackages;
@@ -454,6 +455,39 @@
finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
|| (finishWithRootActivity && r == rootR)) {
+ ActivityRecord topActivity =
+ r.getTask().getTopNonFinishingActivity();
+ boolean passesAsmChecks = topActivity != null
+ && topActivity.getUid() == r.getUid();
+ if (!passesAsmChecks) {
+ Slog.i(TAG, "Finishing task from background. r: " + r);
+ FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+ /* caller_uid */
+ r.getUid(),
+ /* caller_activity_class_name */
+ r.info.name,
+ /* target_task_top_activity_uid */
+ topActivity == null ? -1 : topActivity.getUid(),
+ /* target_task_top_activity_class_name */
+ topActivity == null ? null : topActivity.info.name,
+ /* target_task_is_different */
+ false,
+ /* target_activity_uid */
+ -1,
+ /* target_activity_class_name */
+ null,
+ /* target_intent_action */
+ null,
+ /* target_intent_flags */
+ 0,
+ /* action */
+ FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+ /* version */
+ 1,
+ /* multi_window */
+ false
+ );
+ }
// If requested, remove the task that is associated to this activity only if it
// was the root activity in the task. The result code and data is ignored
// because we don't support returning them across task boundaries. Also, to
@@ -1177,7 +1211,8 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
- r.reportFullyDrawnLocked(restoredFromBundle);
+ mTaskSupervisor.getActivityMetricsLogger().notifyFullyDrawn(r,
+ restoredFromBundle);
}
}
} finally {
@@ -1351,8 +1386,38 @@
}
}
+ /**
+ * Return {@code true} when the given Activity is a relative Task root. That is, the rest of
+ * the Activities in the Task should be finished when it finishes. Otherwise, return {@code
+ * false}.
+ */
+ private boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
+ // Not a relative root if the given Activity is not the root Activity of its TaskFragment.
+ final TaskFragment taskFragment = r.getTaskFragment();
+ if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r,
+ false /* traverseTopToBottom */)) {
+ return false;
+ }
+
+ // The given Activity is the relative Task root if its TaskFragment is a companion
+ // TaskFragment to the taskRoot (i.e. the taskRoot TF will be finished together).
+ return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment;
+ }
+
+ private boolean isTopActivityInTaskFragment(ActivityRecord activity) {
+ return activity.getTaskFragment().topRunningActivity() == activity;
+ }
+
+ private void requestCallbackFinish(IRequestFinishCallback callback) {
+ try {
+ callback.requestFinish();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke request finish callback", e);
+ }
+ }
+
@Override
- public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+ public void onBackPressed(IBinder token, IRequestFinishCallback callback) {
final long origId = Binder.clearCallingIdentity();
try {
final Intent baseActivityIntent;
@@ -1362,20 +1427,29 @@
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) return;
- if (mService.mWindowOrganizerController.mTaskOrganizerController
+ final Task task = r.getTask();
+ final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/,
+ true /*setToBottomIfNone*/);
+ final boolean isTaskRoot = r == root;
+ if (isTaskRoot) {
+ if (mService.mWindowOrganizerController.mTaskOrganizerController
.handleInterceptBackPressedOnTaskRoot(r.getRootTask())) {
- // This task is handled by a task organizer that has requested the back pressed
- // callback.
+ // This task is handled by a task organizer that has requested the back
+ // pressed callback.
+ return;
+ }
+ } else if (!isRelativeTaskRootActivity(r, root)) {
+ // Finish the Activity if the activity is not the task root or relative root.
+ requestCallbackFinish(callback);
return;
}
- final Task task = r.getTask();
- isLastRunningActivity = task.topRunningActivity() == r;
+ isLastRunningActivity = isTopActivityInTaskFragment(isTaskRoot ? root : r);
- final boolean isBaseActivity = r.mActivityComponent.equals(task.realActivity);
- baseActivityIntent = isBaseActivity ? r.intent : null;
+ final boolean isBaseActivity = root.mActivityComponent.equals(task.realActivity);
+ baseActivityIntent = isBaseActivity ? root.intent : null;
- launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
+ launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
}
// If the activity is one of the main entry points for the application, then we should
@@ -1390,16 +1464,12 @@
if (baseActivityIntent != null && isLastRunningActivity
&& ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
|| isLauncherActivity(baseActivityIntent.getComponent()))) {
- moveActivityTaskToBack(token, false /* nonRoot */);
+ moveActivityTaskToBack(token, true /* nonRoot */);
return;
}
// The default option for handling the back button is to finish the Activity.
- try {
- callback.requestFinish();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to invoke request finish callback", e);
- }
+ requestCallbackFinish(callback);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index f0de1d3..e1ab291 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -454,6 +454,7 @@
final int windowsFullyDrawnDelayMs;
final int activityRecordIdHashCode;
final boolean relaunched;
+ final long timestampNs;
private TransitionInfoSnapshot(TransitionInfo info) {
this(info, info.mLastLaunchedActivity, INVALID_DELAY);
@@ -483,6 +484,7 @@
activityRecordIdHashCode = System.identityHashCode(launchedActivity);
this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs;
relaunched = info.mRelaunched;
+ timestampNs = info.mLaunchingState.mStartRealtimeNs;
}
@WaitResult.LaunchState int getLaunchState() {
@@ -498,6 +500,10 @@
}
}
+ boolean isIntresetedToEventLog() {
+ return type == TYPE_TRANSITION_WARM_LAUNCH || type == TYPE_TRANSITION_COLD_LAUNCH;
+ }
+
PackageOptimizationInfo getPackageOptimizationInfo(ArtManagerInternal artManagerInternal) {
return artManagerInternal == null || launchedActivityAppRecordRequiredAbi == null
? PackageOptimizationInfo.createWithNoInfo()
@@ -1022,16 +1028,17 @@
// This will avoid any races with other operations that modify the ActivityRecord.
final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
if (info.isInterestingToLoggerAndObserver()) {
- final long timestampNs = info.mLaunchingState.mStartRealtimeNs;
final long uptimeNs = info.mLaunchingState.mStartUptimeNs;
final int transitionDelay = info.mCurrentTransitionDelayMs;
final int processState = info.mProcessState;
final int processOomAdj = info.mProcessOomAdj;
mLoggerHandler.post(() -> logAppTransition(
- timestampNs, uptimeNs, transitionDelay, infoSnapshot, isHibernating,
+ uptimeNs, transitionDelay, infoSnapshot, isHibernating,
processState, processOomAdj));
}
- mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+ if (infoSnapshot.isIntresetedToEventLog()) {
+ mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+ }
if (info.mPendingFullyDrawn != null) {
info.mPendingFullyDrawn.run();
}
@@ -1040,7 +1047,7 @@
}
// This gets called on another thread without holding the activity manager lock.
- private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeNs,
+ private void logAppTransition(long transitionDeviceUptimeNs,
int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
int processState, int processOomAdj) {
final LogMaker builder = new LogMaker(APP_TRANSITION);
@@ -1108,7 +1115,7 @@
isIncremental,
isLoading,
info.launchedActivityName.hashCode(),
- TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+ TimeUnit.NANOSECONDS.toMillis(info.timestampNs),
processState,
processOomAdj);
@@ -1132,10 +1139,6 @@
}
private void logAppDisplayed(TransitionInfoSnapshot info) {
- if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
- return;
- }
-
EventLog.writeEvent(WM_ACTIVITY_LAUNCH_TIME,
info.userId, info.activityRecordIdHashCode, info.launchedActivityShortComponentName,
info.windowsDrawnDelayMs);
@@ -1181,8 +1184,7 @@
}
/** @see android.app.Activity#reportFullyDrawn */
- TransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r,
- boolean restoredFromBundle) {
+ TransitionInfoSnapshot notifyFullyDrawn(ActivityRecord r, boolean restoredFromBundle) {
final TransitionInfo info = mLastTransitionInfo.get(r);
if (info == null) {
return null;
@@ -1191,7 +1193,7 @@
// There are still undrawn activities, postpone reporting fully drawn until all of its
// windows are drawn. So that is closer to an usable state.
info.mPendingFullyDrawn = () -> {
- logAppTransitionReportedDrawn(r, restoredFromBundle);
+ notifyFullyDrawn(r, restoredFromBundle);
info.mPendingFullyDrawn = null;
};
return null;
@@ -1204,7 +1206,9 @@
currentTimestampNs - info.mLaunchingState.mStartUptimeNs);
final TransitionInfoSnapshot infoSnapshot =
new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
- mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+ if (infoSnapshot.isIntresetedToEventLog()) {
+ mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+ }
mLastTransitionInfo.remove(r);
if (!info.isInterestingToLoggerAndObserver()) {
@@ -1216,46 +1220,8 @@
// fullfils (handling reportFullyDrawn() callbacks).
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"ActivityManager:ReportingFullyDrawn " + info.mLastLaunchedActivity.packageName);
-
- final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
- builder.setPackageName(r.packageName);
- builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
- builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
- builder.setType(restoredFromBundle
- ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
- : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
- builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING,
- info.mProcessRunning ? 1 : 0);
- mMetricsLogger.write(builder);
- final PackageOptimizationInfo packageOptimizationInfo =
- infoSnapshot.getPackageOptimizationInfo(getArtManagerInternal());
- // Incremental info
- boolean isIncremental = false, isLoading = false;
- final String codePath = info.mLastLaunchedActivity.info.applicationInfo.getCodePath();
- if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
- isIncremental = true;
- isLoading = isIncrementalLoading(info.mLastLaunchedActivity.packageName,
- info.mLastLaunchedActivity.mUserId);
- }
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_START_FULLY_DRAWN,
- info.mLastLaunchedActivity.info.applicationInfo.uid,
- info.mLastLaunchedActivity.packageName,
- restoredFromBundle
- ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
- : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
- info.mLastLaunchedActivity.info.name,
- info.mProcessRunning,
- startupTimeMs,
- packageOptimizationInfo.getCompilationReason(),
- packageOptimizationInfo.getCompilationFilter(),
- info.mSourceType,
- info.mSourceEventDelayMs,
- isIncremental,
- isLoading,
- info.mLastLaunchedActivity.info.name.hashCode(),
- TimeUnit.NANOSECONDS.toMillis(info.mLaunchingState.mStartRealtimeNs));
-
+ mLoggerHandler.post(() -> logAppFullyDrawnMetrics(infoSnapshot, restoredFromBundle,
+ info.mProcessRunning));
// Ends the trace started at the beginning of this function. This is located here to allow
// the trace slice to have a noticable duration.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1266,11 +1232,48 @@
return infoSnapshot;
}
- private void logAppFullyDrawn(TransitionInfoSnapshot info) {
- if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
- return;
+ private void logAppFullyDrawnMetrics(TransitionInfoSnapshot info, boolean restoredFromBundle,
+ boolean processRunning) {
+ final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
+ builder.setPackageName(info.packageName);
+ builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivityName);
+ builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS,
+ (long) info.windowsFullyDrawnDelayMs);
+ builder.setType(restoredFromBundle
+ ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
+ : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
+ builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
+ mMetricsLogger.write(builder);
+ final PackageOptimizationInfo packageOptimizationInfo =
+ info.getPackageOptimizationInfo(getArtManagerInternal());
+ // Incremental info
+ boolean isIncremental = false, isLoading = false;
+ final String codePath = info.applicationInfo.getCodePath();
+ if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
+ isIncremental = true;
+ isLoading = isIncrementalLoading(info.packageName, info.userId);
}
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_START_FULLY_DRAWN,
+ info.applicationInfo.uid,
+ info.packageName,
+ restoredFromBundle
+ ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
+ : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
+ info.launchedActivityName,
+ processRunning,
+ info.windowsFullyDrawnDelayMs,
+ packageOptimizationInfo.getCompilationReason(),
+ packageOptimizationInfo.getCompilationFilter(),
+ info.sourceType,
+ info.sourceEventDelayMs,
+ isIncremental,
+ isLoading,
+ info.launchedActivityName.hashCode(),
+ TimeUnit.NANOSECONDS.toMillis(info.timestampNs));
+ }
+ private void logAppFullyDrawn(TransitionInfoSnapshot info) {
StringBuilder sb = mStringBuilder;
sb.setLength(0);
sb.append("Fully drawn ");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0eee8a3..23ed188 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1747,6 +1747,7 @@
}
prevDc.mClosingApps.remove(this);
+ prevDc.getDisplayPolicy().removeRelaunchingApp(this);
if (prevDc.mFocusedApp == this) {
prevDc.setFocusedApp(null);
@@ -3130,8 +3131,8 @@
}
// Check to see if PiP is supported for the display this container is on.
- if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isWindowingModeSupported(
- WINDOWING_MODE_PINNED)) {
+ if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
+ getUid())) {
Slog.w(TAG, "Display " + mDisplayContent.getDisplayId()
+ " doesn't support enter picture-in-picture mode. caller = " + caller);
return false;
@@ -3959,6 +3960,9 @@
void startRelaunching() {
if (mPendingRelaunchCount == 0) {
mRelaunchStartTime = SystemClock.elapsedRealtime();
+ if (mVisibleRequested) {
+ mDisplayContent.getDisplayPolicy().addRelaunchingApp(this);
+ }
}
clearAllDrawn();
@@ -3972,7 +3976,7 @@
mPendingRelaunchCount--;
if (mPendingRelaunchCount == 0 && !isClientVisible()) {
// Don't count if the client won't report drawn.
- mRelaunchStartTime = 0;
+ finishOrAbortReplacingWindow();
}
} else {
// Update keyguard flags upon finishing relaunch.
@@ -3993,7 +3997,12 @@
return;
}
mPendingRelaunchCount = 0;
+ finishOrAbortReplacingWindow();
+ }
+
+ void finishOrAbortReplacingWindow() {
mRelaunchStartTime = 0;
+ mDisplayContent.getDisplayPolicy().removeRelaunchingApp(this);
}
/**
@@ -5102,6 +5111,9 @@
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
logAppCompatState();
+ if (!visible) {
+ finishOrAbortReplacingWindow();
+ }
}
/**
@@ -6485,15 +6497,6 @@
}
}
- void reportFullyDrawnLocked(boolean restoredFromBundle) {
- final TransitionInfoSnapshot info = mTaskSupervisor
- .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
- if (info != null) {
- mTaskSupervisor.reportActivityLaunched(false /* timeout */, this,
- info.windowsFullyDrawnDelayMs, info.getLaunchState());
- }
- }
-
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
@@ -6864,6 +6867,10 @@
if (r == null || r.getParent() == null) {
return INVALID_TASK_ID;
}
+ return getTaskForActivityLocked(r, onlyRoot);
+ }
+
+ static int getTaskForActivityLocked(ActivityRecord r, boolean onlyRoot) {
final Task task = r.task;
if (onlyRoot && r.compareTo(task.getRootActivity(
false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)) > 0) {
@@ -6896,11 +6903,8 @@
* {@link android.view.Display#INVALID_DISPLAY} if not attached.
*/
int getDisplayId() {
- final Task rootTask = getRootTask();
- if (rootTask == null) {
- return INVALID_DISPLAY;
- }
- return rootTask.getDisplayId();
+ return task != null && task.mDisplayContent != null
+ ? task.mDisplayContent.mDisplayId : INVALID_DISPLAY;
}
final boolean isDestroyable() {
@@ -7901,8 +7905,8 @@
}
@Override
- float getSizeCompatScale() {
- return hasSizeCompatBounds() ? mSizeCompatScale : super.getSizeCompatScale();
+ float getCompatScale() {
+ return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
}
@Override
@@ -9204,10 +9208,10 @@
+ " preserveWindow=" + preserveWindow);
if (andResume) {
EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
- task.mTaskId, shortComponentName);
+ task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
} else {
EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
- task.mTaskId, shortComponentName);
+ task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
}
startFreezingScreenLocked(0);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f070064..3ec24d5 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1663,7 +1663,8 @@
}
final Task startedTask = mStartActivity.getTask();
if (newTask) {
- EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId);
+ EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId,
+ startedTask.getRootTaskId(), startedTask.getDisplayId());
}
mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask);
@@ -1856,6 +1857,11 @@
+ " from background: " + mSourceRecord
+ ". New task: " + newTask);
boolean newOrEmptyTask = newTask || (targetTopActivity == null);
+ int action = newTask
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
+ : (mSourceRecord.getTask().equals(targetTask)
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
+ : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
/* caller_uid */
callerUid,
@@ -1874,7 +1880,14 @@
/* target_intent_action */
r.intent.getAction(),
/* target_intent_flags */
- r.intent.getFlags()
+ r.intent.getFlags(),
+ /* action */
+ action,
+ /* version */
+ 1,
+ /* multi_window */
+ targetTask != null && !targetTask.equals(mSourceRecord.getTask())
+ && targetTask.isVisible()
);
}
}
@@ -1976,12 +1989,6 @@
? targetTask.getTopNonFinishingActivity()
: targetTaskTop;
- // At this point we are certain we want the task moved to the front. If we need to dismiss
- // any other always-on-top root tasks, now is the time to do it.
- if (targetTaskTop.canTurnScreenOn() && mService.isDreaming()) {
- targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
- }
-
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
targetTaskTop.showStartingWindow(true /* taskSwitch */);
@@ -1993,6 +2000,12 @@
// And for paranoia, make sure we have correctly resumed the top activity.
resumeTargetRootTaskIfNeeded();
+ // This is moving an existing task to front. But since dream activity has a higher z-order
+ // to cover normal activities, it needs the awakening event to be dismissed.
+ if (mService.isDreaming() && targetTaskTop.canTurnScreenOn()) {
+ targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
+ }
+
mLastStartActivityRecord = targetTaskTop;
return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
@@ -2123,7 +2136,7 @@
mStartActivity.mUserId);
if (act != null) {
final Task task = act.getTask();
- boolean actuallyMoved = task.moveActivityToFrontLocked(act);
+ boolean actuallyMoved = task.moveActivityToFront(act);
if (actuallyMoved) {
// Only record if the activity actually moved.
mMovedToTopActivity = act;
@@ -2732,7 +2745,12 @@
newParent = candidateTf;
}
}
- newParent.mTransitionController.collect(newParent);
+ if (newParent.asTask() == null) {
+ // only collect task-fragments.
+ // TODO(b/258095975): we probably shouldn't ever collect the parent here since it isn't
+ // changing. The logic that changes it should collect it.
+ newParent.mTransitionController.collect(newParent);
+ }
if (mStartActivity.getTaskFragment() == null
|| mStartActivity.getTaskFragment() == newParent) {
newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 4d970f0..ec48643 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -287,8 +287,10 @@
/**
* Called when the device changes its dreaming state.
+ *
+ * @param activeDreamComponent The currently active dream. If null, the device is not dreaming.
*/
- public abstract void notifyDreamStateChanged(boolean dreaming);
+ public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent);
/**
* Set a uid that is allowed to bypass stopped app switches, launching an app
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index b153a85..7dd8770 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -209,7 +209,6 @@
import android.os.WorkSource;
import android.provider.Settings;
import android.service.dreams.DreamActivity;
-import android.service.dreams.DreamManagerInternal;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
import android.sysprop.DisplayProperties;
@@ -669,11 +668,12 @@
private volatile boolean mSleeping;
/**
- * The mDreaming state is set by the {@link DreamManagerService} when it receives a request to
- * start/stop the dream. It is set to true shortly before the {@link DreamService} is started.
- * It is set to false after the {@link DreamService} is stopped.
+ * The mActiveDreamComponent state is set by the {@link DreamManagerService} when it receives a
+ * request to start/stop the dream. It is set to the active dream shortly before the
+ * {@link DreamService} is started. It is set to null after the {@link DreamService} is stopped.
*/
- private volatile boolean mDreaming;
+ @Nullable
+ private volatile ComponentName mActiveDreamComponent;
/**
* The process state used for processes that are running the top activities.
@@ -1439,31 +1439,21 @@
}
boolean isDreaming() {
- return mDreaming;
+ return mActiveDreamComponent != null;
}
boolean canLaunchDreamActivity(String packageName) {
- if (!mDreaming || packageName == null) {
+ if (mActiveDreamComponent == null || packageName == null) {
ProtoLog.e(WM_DEBUG_DREAM, "Cannot launch dream activity due to invalid state. "
- + "dreaming: %b packageName: %s", mDreaming, packageName);
+ + "dream component: %s packageName: %s", mActiveDreamComponent, packageName);
return false;
}
- final DreamManagerInternal dreamManager =
- LocalServices.getService(DreamManagerInternal.class);
- // Verify that the package is the current active dream or doze component. The
- // getActiveDreamComponent() call path does not acquire the DreamManager lock and thus
- // is safe to use.
- final ComponentName activeDream = dreamManager.getActiveDreamComponent(false /* doze */);
- if (activeDream != null && packageName.equals(activeDream.getPackageName())) {
- return true;
- }
- final ComponentName activeDoze = dreamManager.getActiveDreamComponent(true /* doze */);
- if (activeDoze != null && packageName.equals(activeDoze.getPackageName())) {
+ if (packageName.equals(mActiveDreamComponent.getPackageName())) {
return true;
}
ProtoLog.e(WM_DEBUG_DREAM,
- "Dream packageName does not match active dream. Package %s does not match %s or %s",
- packageName, String.valueOf(activeDream), String.valueOf(activeDoze));
+ "Dream packageName does not match active dream. Package %s does not match %s",
+ packageName, String.valueOf(mActiveDreamComponent));
return false;
}
@@ -5680,9 +5670,9 @@
}
@Override
- public void notifyDreamStateChanged(boolean dreaming) {
+ public void notifyActiveDreamChanged(@Nullable ComponentName dreamComponent) {
synchronized (mGlobalLock) {
- mDreaming = dreaming;
+ mActiveDreamComponent = dreamComponent;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3c457e1..2f70eda 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -27,6 +27,7 @@
import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING;
import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WaitResult.INVALID_DELAY;
@@ -2577,6 +2578,10 @@
// Apply options to prevent pendingOptions be taken when scheduling
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
+ if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+ targetActivity.mPendingRemoteAnimation =
+ activityOptions.getRemoteAnimationAdapter();
+ }
targetActivity.applyOptionsAnimation();
if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index a487797..b9a4ed8 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -169,7 +169,8 @@
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
- private final TransitionAnimation mTransitionAnimation;
+ @VisibleForTesting
+ final TransitionAnimation mTransitionAnimation;
private @TransitionFlags int mNextAppTransitionFlags = 0;
private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
@@ -308,10 +309,33 @@
setAppTransitionState(APP_STATE_TIMEOUT);
}
+ /**
+ * Gets the animation overridden by app via {@link #overridePendingAppTransition}.
+ */
+ @Nullable
+ Animation getNextAppRequestedAnimation(boolean enter) {
+ final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
+ mNextAppTransitionPackage,
+ enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
+ if (mNextAppTransitionBackgroundColor != 0 && a != null) {
+ a.setBackdropColor(mNextAppTransitionBackgroundColor);
+ }
+ return a;
+ }
+
+ /**
+ * Gets the animation background color overridden by app via
+ * {@link #overridePendingAppTransition}.
+ */
@ColorInt int getNextAppTransitionBackgroundColor() {
return mNextAppTransitionBackgroundColor;
}
+ @VisibleForTesting
+ boolean isNextAppTransitionOverrideRequested() {
+ return mNextAppTransitionOverrideRequested;
+ }
+
HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
container.hashCode());
@@ -401,9 +425,12 @@
}
void clear() {
+ clear(true /* clearAppOverride */);
+ }
+
+ private void clear(boolean clearAppOverride) {
mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
mNextAppTransitionOverrideRequested = false;
- mNextAppTransitionPackage = null;
mNextAppTransitionAnimationsSpecs.clear();
mRemoteAnimationController = null;
mNextAppTransitionAnimationsSpecsFuture = null;
@@ -411,6 +438,12 @@
mAnimationFinishedCallback = null;
mOverrideTaskTransition = false;
mNextAppTransitionIsSync = false;
+ if (clearAppOverride) {
+ mNextAppTransitionPackage = null;
+ mNextAppTransitionEnter = 0;
+ mNextAppTransitionExit = 0;
+ mNextAppTransitionBackgroundColor = 0;
+ }
}
void freeze() {
@@ -516,7 +549,7 @@
return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
}
- static int mapOpenCloseTransitTypes(int transit, boolean enter) {
+ private static int mapOpenCloseTransitTypes(int transit, boolean enter) {
int animAttr = 0;
switch (transit) {
case TRANSIT_OLD_ACTIVITY_OPEN:
@@ -776,11 +809,7 @@
"applyAnimation: anim=%s transit=%s Callers=%s", a,
appTransitionOldToString(transit), Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage,
- enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
- if (mNextAppTransitionBackgroundColor != 0) {
- a.setBackdropColor(mNextAppTransitionBackgroundColor);
- }
+ a = getNextAppRequestedAnimation(enter);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
+ "isEntrance=%b Callers=%s",
@@ -1020,7 +1049,9 @@
ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
isTransitionSet(), remoteAnimationAdapter);
if (isTransitionSet() && !mNextAppTransitionIsSync) {
- clear();
+ // ActivityEmbedding animation will run by the app process for which we want to respect
+ // the app override for whether or not to show background color.
+ clear(!isActivityEmbedding /* clearAppOverride */);
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
remoteAnimationAdapter, mHandler, isActivityEmbedding);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 9011533..5380de7 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -215,9 +215,20 @@
mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
mDisplayContent.mOpeningApps);
+ ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
+ ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
+ if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) {
+ tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
+ tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
+ if (mDisplayContent.mAtmService.mBackNavigationController
+ .removeIfContainsBackAnimationTargets(tmpOpenApps, tmpCloseApps)) {
+ mDisplayContent.mAtmService.mBackNavigationController.clearBackAnimations(null);
+ }
+ }
+
@TransitionOldType final int transit = getTransitCompatType(
- mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,
+ mDisplayContent.mAppTransition, tmpOpenApps,
+ tmpCloseApps, mDisplayContent.mChangingContainers,
mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
mDisplayContent.mSkipAppTransitionAnimation);
mDisplayContent.mSkipAppTransitionAnimation = false;
@@ -225,22 +236,21 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"handleAppTransitionReady: displayId=%d appTransition={%s}"
+ " openingApps=[%s] closingApps=[%s] transit=%s",
- mDisplayContent.mDisplayId, appTransition.toString(), mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps, AppTransition.appTransitionOldToString(transit));
+ mDisplayContent.mDisplayId, appTransition.toString(), tmpOpenApps,
+ tmpCloseApps, AppTransition.appTransitionOldToString(transit));
// Find the layout params of the top-most application window in the tokens, which is
// what will control the animation theme. If all closing windows are obscured, then there is
// no need to do an animation. This is the case, for example, when this transition is being
// done behind a dream window.
- final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);
+ final ArraySet<Integer> activityTypes = collectActivityTypes(tmpOpenApps,
+ tmpCloseApps, mDisplayContent.mChangingContainers);
final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers);
+ tmpOpenApps, tmpCloseApps, mDisplayContent.mChangingContainers);
final ActivityRecord topOpeningApp =
- getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
+ getTopApp(tmpOpenApps, false /* ignoreHidden */);
final ActivityRecord topClosingApp =
- getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
+ getTopApp(tmpCloseApps, false /* ignoreHidden */);
final ActivityRecord topChangingApp =
getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
@@ -258,8 +268,7 @@
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
try {
- applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
- animLp, voiceInteraction);
+ applyAnimations(tmpOpenApps, tmpCloseApps, transit, animLp, voiceInteraction);
handleClosingApps();
handleOpeningApps();
handleChangingApps(transit);
@@ -561,6 +570,34 @@
}
/**
+ * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
+ * parent {@link Task} before or after the transition.
+ */
+ private boolean transitionContainsTaskFragmentWithBoundsOverride() {
+ for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+ final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
+ if (wc.isEmbedded()) {
+ // Contains embedded TaskFragment with bounds changed.
+ return true;
+ }
+ }
+ mTempTransitionWindows.clear();
+ mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+ boolean containsTaskFragmentWithBoundsOverride = false;
+ for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
+ final TaskFragment tf = r.getTaskFragment();
+ if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
+ containsTaskFragmentWithBoundsOverride = true;
+ break;
+ }
+ }
+ mTempTransitionWindows.clear();
+ return containsTaskFragmentWithBoundsOverride;
+ }
+
+ /**
* Finds the common parent {@link Task} that is parent of all embedded app windows in the
* current transition.
* @return {@code null} if app windows in the transition are not children of the same Task, or
@@ -663,12 +700,17 @@
if (transitionMayContainNonAppWindows(transit)) {
return false;
}
+ if (!transitionContainsTaskFragmentWithBoundsOverride()) {
+ // No need to play TaskFragment remote animation if all embedded TaskFragment in the
+ // transition fill the Task.
+ return false;
+ }
final Task task = findParentTaskForAllEmbeddedWindows();
final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
final RemoteAnimationDefinition definition = organizer != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer, task.mTaskId)
+ .getRemoteAnimationDefinition(organizer)
: null;
final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 6ff2bb3..f5da4c8 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,6 +19,9 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
@@ -32,6 +35,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationTarget;
@@ -41,7 +45,6 @@
import android.window.IBackAnimationFinishedCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.TaskSnapshot;
-import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -60,9 +63,9 @@
private boolean mShowWallpaper;
private Runnable mPendingAnimation;
- // TODO (b/241808055) Find a appropriate time to remove during refactor
- // Execute back animation with legacy transition system. Temporary flag for easier debugging.
- static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions;
+ private final AnimationTargets mAnimationTargets = new AnimationTargets();
+ private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
+ private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>();
/**
* true if the back predictability feature is enabled
@@ -284,7 +287,6 @@
}
if (prepareAnimation) {
- infoBuilder.setDepartingWCT(toWindowContainerToken(currentTask));
prepareAnimationIfNeeded(currentTask, prevTask, prevActivity,
removedWindowContainer, backType, adapter);
}
@@ -303,11 +305,233 @@
return infoBuilder.build();
}
- private static WindowContainerToken toWindowContainerToken(WindowContainer<?> windowContainer) {
- if (windowContainer == null || windowContainer.mRemoteToken == null) {
- return null;
+ boolean isWaitBackTransition() {
+ return mAnimationTargets.mComposed && mAnimationTargets.mWaitTransition;
+ }
+
+ // For legacy transition.
+ /**
+ * Once we find the transition targets match back animation targets, remove the target from
+ * list, so that transition won't count them in since the close animation was finished.
+ *
+ * @return {@code true} if the participants of this transition was animated by back gesture
+ * animations, and shouldn't join next transition.
+ */
+ boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
+ ArraySet<ActivityRecord> closeApps) {
+ if (!isWaitBackTransition()) {
+ return false;
}
- return windowContainer.mRemoteToken.toWindowContainerToken();
+ mTmpCloseApps.addAll(closeApps);
+ boolean result = false;
+ // Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from
+ // mOpeningApps if there is no visibility change.
+ if (mAnimationTargets.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) {
+ // remove close target from close list, open target from open list;
+ // but the open target can be in close list.
+ for (int i = openApps.size() - 1; i >= 0; --i) {
+ final ActivityRecord ar = openApps.valueAt(i);
+ if (mAnimationTargets.isTarget(ar, true /* open */)) {
+ openApps.removeAt(i);
+ }
+ }
+ for (int i = closeApps.size() - 1; i >= 0; --i) {
+ final ActivityRecord ar = closeApps.valueAt(i);
+ if (mAnimationTargets.isTarget(ar, false /* open */)) {
+ closeApps.removeAt(i);
+ }
+ }
+ result = true;
+ }
+ mTmpCloseApps.clear();
+ return result;
+ }
+
+ // For shell transition
+ /**
+ * Check whether the transition targets was animated by back gesture animation.
+ * Because the opening target could request to do other stuff at onResume, so it could become
+ * close target for a transition. So the condition here is
+ * The closing target should only exist in close list, but the opening target can be either in
+ * open or close list.
+ * @return {@code true} if the participants of this transition was animated by back gesture
+ * animations, and shouldn't join next transition.
+ */
+ boolean containsBackAnimationTargets(Transition transition) {
+ if (!mAnimationTargets.mComposed
+ || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) {
+ return false;
+ }
+ final ArraySet<WindowContainer> targets = transition.mParticipants;
+ for (int i = targets.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = targets.valueAt(i);
+ if (wc.asActivityRecord() == null && wc.asTask() == null) {
+ continue;
+ }
+ // WC can be visible due to setLaunchBehind
+ if (wc.isVisibleRequested()) {
+ mTmpOpenApps.add(wc);
+ } else {
+ mTmpCloseApps.add(wc);
+ }
+ }
+ final boolean result = mAnimationTargets.containsBackAnimationTargets(
+ mTmpOpenApps, mTmpCloseApps);
+ mTmpOpenApps.clear();
+ mTmpCloseApps.clear();
+ return result;
+ }
+
+ boolean isMonitorTransitionTarget(WindowContainer wc) {
+ if (!mAnimationTargets.mComposed || !mAnimationTargets.mWaitTransition) {
+ return false;
+ }
+ return mAnimationTargets.isTarget(wc, wc.isVisibleRequested() /* open */);
+ }
+
+ /**
+ * Cleanup animation, this can either happen when transition ready or finish.
+ * @param cleanupTransaction The transaction which the caller want to apply the internal
+ * cleanup together.
+ */
+ void clearBackAnimations(SurfaceControl.Transaction cleanupTransaction) {
+ mAnimationTargets.clearBackAnimateTarget(cleanupTransaction);
+ }
+
+ /**
+ * TODO: Animation composer
+ * prepareAnimationIfNeeded will become too complicated in order to support
+ * ActivityRecord/WindowState, using a factory class to create the RemoteAnimationTargets for
+ * different scenario.
+ */
+ private static class AnimationTargets {
+ ActivityRecord mCloseTarget; // Must be activity
+ WindowContainer mOpenTarget; // Can be activity or task if activity was removed
+ private boolean mComposed;
+ private boolean mWaitTransition;
+ private int mSwitchType = UNKNOWN;
+ private SurfaceControl.Transaction mFinishedTransaction;
+
+ private static final int UNKNOWN = 0;
+ private static final int TASK_SWITCH = 1;
+ private static final int ACTIVITY_SWITCH = 2;
+
+ void reset(@NonNull WindowContainer close, @NonNull WindowContainer open) {
+ clearBackAnimateTarget(null);
+ if (close.asActivityRecord() != null && open.asActivityRecord() != null
+ && (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) {
+ mSwitchType = ACTIVITY_SWITCH;
+ mCloseTarget = close.asActivityRecord();
+ } else if (close.asTask() != null && open.asTask() != null
+ && close.asTask() != open.asTask()) {
+ mSwitchType = TASK_SWITCH;
+ mCloseTarget = close.asTask().getTopNonFinishingActivity();
+ } else {
+ mSwitchType = UNKNOWN;
+ return;
+ }
+
+ mOpenTarget = open;
+ mComposed = false;
+ mWaitTransition = false;
+ }
+
+ void composeNewAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open) {
+ reset(close, open);
+ if (mSwitchType == UNKNOWN || mComposed || mCloseTarget == mOpenTarget
+ || mCloseTarget == null || mOpenTarget == null) {
+ return;
+ }
+ mComposed = true;
+ mWaitTransition = false;
+ }
+
+ boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) {
+ for (int i = wcs.size() - 1; i >= 0; --i) {
+ if (isTarget(wcs.get(i), open)) {
+ return true;
+ }
+ }
+ return wcs.isEmpty();
+ }
+
+ boolean isTarget(WindowContainer wc, boolean open) {
+ if (open) {
+ return wc == mOpenTarget || mOpenTarget.hasChild(wc);
+ }
+ if (mSwitchType == TASK_SWITCH) {
+ return wc == mCloseTarget
+ || (wc.asTask() != null && wc.hasChild(mCloseTarget));
+ } else if (mSwitchType == ACTIVITY_SWITCH) {
+ return wc == mCloseTarget;
+ }
+ return false;
+ }
+
+ boolean setFinishTransaction(SurfaceControl.Transaction finishTransaction) {
+ if (!mComposed) {
+ return false;
+ }
+ mFinishedTransaction = finishTransaction;
+ return true;
+ }
+
+ void finishPresentAnimations(SurfaceControl.Transaction t) {
+ if (!mComposed) {
+ return;
+ }
+ final SurfaceControl.Transaction pt = t != null ? t
+ : mOpenTarget.getPendingTransaction();
+ if (mFinishedTransaction != null) {
+ pt.merge(mFinishedTransaction);
+ mFinishedTransaction = null;
+ }
+ }
+
+ void clearBackAnimateTarget(SurfaceControl.Transaction cleanupTransaction) {
+ finishPresentAnimations(cleanupTransaction);
+ mCloseTarget = null;
+ mOpenTarget = null;
+ mComposed = false;
+ mWaitTransition = false;
+ mSwitchType = UNKNOWN;
+ if (mFinishedTransaction != null) {
+ Slog.w(TAG, "Clear back animation, found un-processed finished transaction");
+ if (cleanupTransaction != null) {
+ cleanupTransaction.merge(mFinishedTransaction);
+ } else {
+ mFinishedTransaction.apply();
+ }
+ mFinishedTransaction = null;
+ }
+ }
+
+ // The close target must in close list
+ // The open target can either in close or open list
+ boolean containsBackAnimationTargets(ArrayList<WindowContainer> openApps,
+ ArrayList<WindowContainer> closeApps) {
+ return containTarget(closeApps, false /* open */)
+ && (containTarget(openApps, true /* open */)
+ || containTarget(openApps, false /* open */));
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(128);
+ sb.append("AnimationTargets{");
+ sb.append(" mOpenTarget= ");
+ sb.append(mOpenTarget);
+ sb.append(" mCloseTarget= ");
+ sb.append(mCloseTarget);
+ sb.append(" mSwitchType= ");
+ sb.append(mSwitchType);
+ sb.append(" mComposed= ");
+ sb.append(mComposed);
+ sb.append(" mWaitTransition= ");
+ sb.append(mWaitTransition);
+ sb.append('}');
+ return sb.toString();
+ }
}
private void prepareAnimationIfNeeded(Task currentTask,
@@ -373,10 +597,6 @@
leashes.add(screenshotSurface);
}
} else if (prevTask != null) {
- if (!ENABLE_SHELL_TRANSITIONS) {
- // Special handling for preventing next transition.
- currentTask.mBackGestureStarted = true;
- }
prevActivity = prevTask.getTopNonFinishingActivity();
if (prevActivity != null) {
// Make previous task show from behind by marking its top activity as visible
@@ -417,35 +637,36 @@
for (SurfaceControl sc: leashes) {
finishedTransaction.remove(sc);
}
-
synchronized (mWindowManagerService.mGlobalLock) {
- if (ENABLE_SHELL_TRANSITIONS) {
- if (!triggerBack) {
- if (!needsScreenshot(backType)) {
- restoreLaunchBehind(finalPrevActivity);
- }
+ if (triggerBack) {
+ final SurfaceControl surfaceControl =
+ removedWindowContainer.getSurfaceControl();
+ if (surfaceControl != null && surfaceControl.isValid()) {
+ // The animation is finish and start waiting for transition,
+ // hide the task surface before it re-parented to avoid flicker.
+ finishedTransaction.hide(surfaceControl);
}
+ } else if (!needsScreenshot(backType)) {
+ restoreLaunchBehind(finalPrevActivity);
+ }
+ if (!mAnimationTargets.setFinishTransaction(finishedTransaction)) {
+ finishedTransaction.apply();
+ }
+ if (!triggerBack) {
+ mAnimationTargets.clearBackAnimateTarget(null);
} else {
- if (triggerBack) {
- final SurfaceControl surfaceControl =
- removedWindowContainer.getSurfaceControl();
- if (surfaceControl != null && surfaceControl.isValid()) {
- // When going back to home, hide the task surface before it
- // re-parented to avoid flicker.
- finishedTransaction.hide(surfaceControl);
- }
- } else {
- currentTask.mBackGestureStarted = false;
- if (!needsScreenshot(backType)) {
- restoreLaunchBehind(finalPrevActivity);
- }
- }
+ mAnimationTargets.mWaitTransition = true;
}
}
- finishedTransaction.apply();
+ // TODO Add timeout monitor if transition didn't happen
}
};
-
+ if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
+ mAnimationTargets.composeNewAnimations(removedWindowContainer, prevActivity);
+ } else if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ || backType == BackNavigationInfo.TYPE_CROSS_TASK) {
+ mAnimationTargets.composeNewAnimations(removedWindowContainer, prevTask);
+ }
scheduleAnimationLocked(backType, targets, adapter, callback);
}
@@ -611,9 +832,8 @@
}
boolean isWallpaperVisible(WindowState w) {
- if (mBackAnimationInProgress && w.isFocused()) {
- return mShowWallpaper;
- }
- return false;
+ return mAnimationTargets.mComposed && mShowWallpaper
+ && w.mAttrs.type == TYPE_BASE_APPLICATION && w.mActivityRecord != null
+ && mAnimationTargets.isTarget(w.mActivityRecord, true /* open */);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b84b2d8..bedeabe 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -369,6 +369,55 @@
}
@Override
+ ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
+ ActivityRecord boundary) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return null;
+ }
+ return super.getActivity(callback, traverseTopToBottom, boundary);
+ }
+
+ @Override
+ Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return null;
+ }
+ return super.getTask(callback, traverseTopToBottom);
+ }
+
+ @Override
+ boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllActivities(callback, traverseTopToBottom);
+ }
+
+ @Override
+ boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllRootTasks(callback, traverseTopToBottom);
+ }
+
+ @Override
+ boolean forAllTasks(Predicate<Task> callback) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllTasks(callback);
+ }
+
+ @Override
+ boolean forAllLeafTasks(Predicate<Task> callback) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllLeafTasks(callback);
+ }
+
+ @Override
void forAllDisplayAreas(Consumer<DisplayArea> callback) {
super.forAllDisplayAreas(callback);
callback.accept(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8b34443..e65ea53 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -230,6 +230,7 @@
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.view.inputmethod.ImeTracker;
import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.ScreenCapture;
@@ -310,6 +311,9 @@
*/
private SurfaceControl mOverlayLayer;
+ /** A surfaceControl specifically for accessibility overlays. */
+ private SurfaceControl mA11yOverlayLayer;
+
/**
* The direct child layer of the display to put all non-overlay windows. This is also used for
* screen rotation animation so that there is a parent layer to put the animation leash.
@@ -454,7 +458,7 @@
/**
* Compat metrics computed based on {@link #mDisplayMetrics}.
- * @see #updateDisplayAndOrientation(int, Configuration)
+ * @see #updateDisplayAndOrientation(Configuration)
*/
private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
@@ -1268,12 +1272,21 @@
transaction.reparent(mOverlayLayer, mSurfaceControl);
}
+ if (mA11yOverlayLayer == null) {
+ mA11yOverlayLayer =
+ b.setName("Accessibility Overlays").setParent(mSurfaceControl).build();
+ } else {
+ transaction.reparent(mA11yOverlayLayer, mSurfaceControl);
+ }
+
transaction
.setLayer(mSurfaceControl, 0)
.setLayerStack(mSurfaceControl, mDisplayId)
.show(mSurfaceControl)
.setLayer(mOverlayLayer, Integer.MAX_VALUE)
- .show(mOverlayLayer);
+ .show(mOverlayLayer)
+ .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 1)
+ .show(mA11yOverlayLayer);
}
boolean isReady() {
@@ -3249,6 +3262,7 @@
setRemoteInsetsController(null);
mWmService.mAnimator.removeDisplayLocked(mDisplayId);
mOverlayLayer.release();
+ mA11yOverlayLayer.release();
mWindowingLayer.release();
mInputMonitor.onDisplayRemoved();
mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
@@ -5031,7 +5045,7 @@
* layer has been assigned since), to facilitate assigning the layer from the IME target, or
* fall back if there is no target.
* - the container doesn't always participate in window traversal, according to
- * {@link #skipImeWindowsDuringTraversal()}
+ * {@link #skipImeWindowsDuringTraversal(DisplayContent)}
*/
private static class ImeContainer extends DisplayArea.Tokens {
boolean mNeedsLayer = false;
@@ -5501,6 +5515,10 @@
return mOverlayLayer;
}
+ SurfaceControl getA11yOverlayLayer() {
+ return mA11yOverlayLayer;
+ }
+
SurfaceControl[] findRoundedCornerOverlays() {
List<SurfaceControl> roundedCornerOverlays = new ArrayList<>();
for (WindowToken token : mTokenMap.values()) {
@@ -6712,25 +6730,35 @@
mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
stateController.getControlsForDispatch(this));
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver inset state change", e);
+ Slog.w(TAG, "Failed to deliver inset control state change", e);
}
}
@Override
- public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mRemoteInsetsController.showInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
+ mRemoteInsetsController.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mRemoteInsetsController.hideInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
+ mRemoteInsetsController.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver showInsets", e);
+ Slog.w(TAG, "Failed to deliver hideInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7bcea36..1fef3c2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -270,6 +270,12 @@
private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();
+ /** Apps which are controlling the appearance of system bars */
+ private final ArraySet<ActivityRecord> mSystemBarColorApps = new ArraySet<>();
+
+ /** Apps which are relaunching and were controlling the appearance of system bars */
+ private final ArraySet<ActivityRecord> mRelaunchingSystemBarColorApps = new ArraySet<>();
+
private boolean mIsFreeformWindowOverlappingWithNavBar;
private boolean mLastImmersiveMode;
@@ -1449,6 +1455,7 @@
mStatusBarBackgroundWindows.clear();
mStatusBarColorCheckedBounds.setEmpty();
mStatusBarBackgroundCheckedBounds.setEmpty();
+ mSystemBarColorApps.clear();
mAllowLockscreenWhenOn = false;
mShowingDream = false;
@@ -1525,6 +1532,7 @@
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
+ addSystemBarColorApp(win);
}
}
@@ -1537,6 +1545,7 @@
if (isOverlappingWithNavBar) {
if (mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
+ addSystemBarColorApp(win);
}
if (mNavBarBackgroundWindow == null) {
mNavBarBackgroundWindow = win;
@@ -1555,9 +1564,11 @@
}
} else if (win.isDimming()) {
if (mStatusBar != null) {
- addStatusBarAppearanceRegionsForDimmingWindow(
+ if (addStatusBarAppearanceRegionsForDimmingWindow(
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
- mStatusBar.getFrame(), win.getBounds(), win.getFrame());
+ mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
+ addSystemBarColorApp(win);
+ }
}
if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
@@ -1565,18 +1576,21 @@
}
}
- private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame,
- Rect winBounds, Rect winFrame) {
+ /**
+ * Returns true if mStatusBarAppearanceRegionList is changed.
+ */
+ private boolean addStatusBarAppearanceRegionsForDimmingWindow(
+ int appearance, Rect statusBarFrame, Rect winBounds, Rect winFrame) {
if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) {
- return;
+ return false;
}
if (mStatusBarColorCheckedBounds.contains(sTmpRect)) {
- return;
+ return false;
}
if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) {
mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds)));
mStatusBarColorCheckedBounds.union(sTmpRect);
- return;
+ return true;
}
// A dimming window can divide status bar into different appearance regions (up to 3).
// +---------+-------------+---------+
@@ -1605,6 +1619,14 @@
// We don't have vertical status bar yet, so we don't handle the other orientation.
}
mStatusBarColorCheckedBounds.union(sTmpRect);
+ return true;
+ }
+
+ private void addSystemBarColorApp(WindowState win) {
+ final ActivityRecord app = win.mActivityRecord;
+ if (app != null) {
+ mSystemBarColorApps.add(app);
+ }
}
/**
@@ -1638,7 +1660,16 @@
*/
private void applyKeyguardPolicy(WindowState win, WindowState imeTarget) {
if (win.canBeHiddenByKeyguard()) {
- if (shouldBeHiddenByKeyguard(win, imeTarget)) {
+ final boolean shouldBeHiddenByKeyguard = shouldBeHiddenByKeyguard(win, imeTarget);
+ if (win.mIsImWindow) {
+ // Notify IME insets provider to freeze the IME insets. In case when turning off
+ // the screen, the IME insets source window will be hidden because of keyguard
+ // policy change and affects the system to freeze the last insets state. (And
+ // unfreeze when the IME is going to show)
+ mDisplayContent.getInsetsStateController().getImeSourceProvider().setFrozen(
+ shouldBeHiddenByKeyguard);
+ }
+ if (shouldBeHiddenByKeyguard) {
win.hide(false /* doAnimation */, true /* requestAnim */);
} else {
win.show(false /* doAnimation */, true /* requestAnim */);
@@ -2040,7 +2071,8 @@
// Don't show status bar when swiping on already visible navigation bar.
// But restore the position of navigation bar if it has been moved by the control
// target.
- controlTarget.showInsets(Type.navigationBars(), false);
+ controlTarget.showInsets(Type.navigationBars(), false /* fromIme */,
+ null /* statsToken */);
return;
}
@@ -2048,10 +2080,12 @@
// Show transient bars if they are hidden; restore position if they are visible.
mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE,
isGestureOnSystemBar);
- controlTarget.showInsets(restorePositionTypes, false);
+ controlTarget.showInsets(restorePositionTypes, false /* fromIme */,
+ null /* statsToken */);
} else {
// Restore visibilities and positions of system bars.
- controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false);
+ controlTarget.showInsets(Type.statusBars() | Type.navigationBars(),
+ false /* fromIme */, null /* statsToken */);
// To further allow the pull-down-from-the-top gesture to pull down the notification
// shade as a consistent motion, we reroute the touch events here from the currently
// touched window to the status bar after making it visible.
@@ -2077,6 +2111,25 @@
return mDisplayContent.getInsetsPolicy();
}
+ /**
+ * Called when an app has started replacing its main window.
+ */
+ void addRelaunchingApp(ActivityRecord app) {
+ if (mSystemBarColorApps.contains(app)) {
+ mRelaunchingSystemBarColorApps.add(app);
+ }
+ }
+
+ /**
+ * Called when an app has finished replacing its main window or aborted.
+ */
+ void removeRelaunchingApp(ActivityRecord app) {
+ final boolean removed = mRelaunchingSystemBarColorApps.remove(app);
+ if (removed & mRelaunchingSystemBarColorApps.isEmpty()) {
+ updateSystemBarAttributes();
+ }
+ }
+
void resetSystemBarAttributes() {
mLastDisableFlags = 0;
updateSystemBarAttributes();
@@ -2119,6 +2172,11 @@
final int displayId = getDisplayId();
final int disableFlags = win.getDisableFlags();
final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
+ if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+ // The appearance of system bars might change while relaunching apps. We don't report
+ // the intermediate state to system UI. Otherwise, it might trigger redundant effects.
+ return;
+ }
final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
final boolean isNavbarColorManagedByIme =
@@ -2581,6 +2639,14 @@
pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
pw.println(mTopFullscreenOpaqueWindowState);
}
+ if (!mSystemBarColorApps.isEmpty()) {
+ pw.print(prefix); pw.print("mSystemBarColorApps=");
+ pw.println(mSystemBarColorApps);
+ }
+ if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+ pw.print(prefix); pw.print("mRelaunchingSystemBarColorApps=");
+ pw.println(mRelaunchingSystemBarColorApps);
+ }
if (mNavBarColorWindowCandidate != null) {
pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
pw.println(mNavBarColorWindowCandidate);
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 5d49042..6f821b5 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -162,6 +162,17 @@
return mDisplayWindowPolicyController.canShowTasksInRecents();
}
+ /**
+ * @see DisplayWindowPolicyController#isEnteringPipAllowed(int)
+ */
+ public final boolean isEnteringPipAllowed(int uid) {
+ if (mDisplayWindowPolicyController == null) {
+ return true;
+ }
+ return mDisplayWindowPolicyController.isEnteringPipAllowed(uid);
+ }
+
+
void dump(String prefix, PrintWriter pw) {
if (mDisplayWindowPolicyController != null) {
pw.println();
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index e0644b6..b735b30 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -442,11 +442,12 @@
mRemoveContentMode = other.mRemoveContentMode;
changed = true;
}
- if (other.mShouldShowWithInsecureKeyguard != mShouldShowWithInsecureKeyguard) {
+ if (!Objects.equals(
+ other.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
mShouldShowWithInsecureKeyguard = other.mShouldShowWithInsecureKeyguard;
changed = true;
}
- if (other.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+ if (!Objects.equals(other.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
mShouldShowSystemDecors = other.mShouldShowSystemDecors;
changed = true;
}
@@ -458,15 +459,15 @@
mFixedToUserRotation = other.mFixedToUserRotation;
changed = true;
}
- if (other.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+ if (!Objects.equals(other.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
changed = true;
}
- if (other.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+ if (!Objects.equals(other.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
mIgnoreDisplayCutout = other.mIgnoreDisplayCutout;
changed = true;
}
- if (other.mDontMoveToTop != mDontMoveToTop) {
+ if (!Objects.equals(other.mDontMoveToTop, mDontMoveToTop)) {
mDontMoveToTop = other.mDontMoveToTop;
changed = true;
}
@@ -522,14 +523,13 @@
mRemoveContentMode = delta.mRemoveContentMode;
changed = true;
}
- if (delta.mShouldShowWithInsecureKeyguard != null
- && delta.mShouldShowWithInsecureKeyguard
- != mShouldShowWithInsecureKeyguard) {
+ if (delta.mShouldShowWithInsecureKeyguard != null && !Objects.equals(
+ delta.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
mShouldShowWithInsecureKeyguard = delta.mShouldShowWithInsecureKeyguard;
changed = true;
}
- if (delta.mShouldShowSystemDecors != null
- && delta.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+ if (delta.mShouldShowSystemDecors != null && !Objects.equals(
+ delta.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
mShouldShowSystemDecors = delta.mShouldShowSystemDecors;
changed = true;
}
@@ -543,18 +543,18 @@
mFixedToUserRotation = delta.mFixedToUserRotation;
changed = true;
}
- if (delta.mIgnoreOrientationRequest != null
- && delta.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+ if (delta.mIgnoreOrientationRequest != null && !Objects.equals(
+ delta.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
mIgnoreOrientationRequest = delta.mIgnoreOrientationRequest;
changed = true;
}
- if (delta.mIgnoreDisplayCutout != null
- && delta.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+ if (delta.mIgnoreDisplayCutout != null && !Objects.equals(
+ delta.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
mIgnoreDisplayCutout = delta.mIgnoreDisplayCutout;
changed = true;
}
- if (delta.mDontMoveToTop != null
- && delta.mDontMoveToTop != mDontMoveToTop) {
+ if (delta.mDontMoveToTop != null && !Objects.equals(
+ delta.mDontMoveToTop, mDontMoveToTop)) {
mDontMoveToTop = delta.mDontMoveToTop;
changed = true;
}
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 1e5a219..d94bf4b 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -8,11 +8,11 @@
# An activity is being finished:
30001 wm_finish_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# A task is being brought to the front of the screen:
-30002 wm_task_to_front (User|1|5),(Task|1|5)
+30002 wm_task_to_front (User|1|5),(Task|1|5),(Display Id|1|5)
# An existing activity is being given a new intent:
30003 wm_new_intent (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# A new task is being created:
-30004 wm_create_task (User|1|5),(Task ID|1|5)
+30004 wm_create_task (User|1|5),(Task ID|1|5),(Root Task ID|1|5),(Display Id|1|5)
# A new activity is being created in an existing task:
30005 wm_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# An activity has been resumed into the foreground but was not already running:
@@ -32,9 +32,9 @@
# An activity is being destroyed:
30018 wm_destroy_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# An activity has been relaunched, resumed, and is now in the foreground:
-30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3)
# An activity has been relaunched:
-30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3)
# Activity set to resumed
30043 wm_set_resumed_activity (User|1|5),(Component Name|3),(Reason|3)
@@ -45,9 +45,6 @@
# Attempting to stop an activity
30048 wm_stop_activity (User|1|5),(Token|1|5),(Component Name|3)
-# The task is being removed from its parent task
-30061 wm_remove_task (Task ID|1|5), (Root Task ID|1|5)
-
# An activity been add into stopping list
30066 wm_add_to_stopping (User|1|5),(Token|1|5),(Component Name|3),(Reason|3)
@@ -57,11 +54,11 @@
# Out of memory for surfaces.
31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3)
# Task created.
-31001 wm_task_created (TaskId|1|5),(RootTaskId|1|5)
+31001 wm_task_created (TaskId|1|5)
# Task moved to top (1) or bottom (0).
-31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
+31002 wm_task_moved (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(ToTop|1),(Index|1)
# Task removed with source explanation.
-31003 wm_task_removed (TaskId|1|5),(Reason|3)
+31003 wm_task_removed (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(Reason|3)
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 38eca35..7fd093f 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -33,9 +33,11 @@
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
+import android.view.InsetsSourceConsumer;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,12 +51,21 @@
*/
final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
+ /** The token tracking the current IME request or {@code null} otherwise. */
+ @Nullable
+ private ImeTracker.Token mImeRequesterStatsToken;
private InsetsControlTarget mImeRequester;
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
private boolean mImeShowing;
private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME);
+ /** @see #setFrozen(boolean) */
+ private boolean mFrozen;
+
+ /** @see #setServerVisible(boolean) */
+ private boolean mServerVisible;
+
ImeInsetsSourceProvider(InsetsSource source,
InsetsStateController stateController, DisplayContent displayContent) {
super(source, stateController, displayContent);
@@ -81,6 +92,32 @@
}
@Override
+ void setServerVisible(boolean serverVisible) {
+ mServerVisible = serverVisible;
+ if (!mFrozen) {
+ super.setServerVisible(serverVisible);
+ }
+ }
+
+ /**
+ * Freeze IME insets source state when required.
+ *
+ * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the
+ * current IME insets state and pending the IME insets state update until setting
+ * {@param frozen} as {@code false}.
+ */
+ void setFrozen(boolean frozen) {
+ if (mFrozen == frozen) {
+ return;
+ }
+ mFrozen = frozen;
+ if (!frozen) {
+ // Unfreeze and process the pending IME insets states.
+ super.setServerVisible(mServerVisible);
+ }
+ }
+
+ @Override
void updateSourceFrame(Rect frame) {
super.updateSourceFrame(frame);
onSourceChanged();
@@ -130,14 +167,20 @@
}
/**
- * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
- * requests to show IME on {@param imeTarget}.
+ * Called from {@link WindowManagerInternal#showImePostLayout}
+ * when {@link android.inputmethodservice.InputMethodService} requests to show IME
+ * on {@param imeTarget}.
*
- * @param imeTarget imeTarget on which IME request is coming from.
+ * @param imeTarget imeTarget on which IME show request is coming from.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
+ void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
+ @Nullable ImeTracker.Token statsToken) {
boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
mImeRequester = imeTarget;
+ // There was still a stats token, so that request presumably failed.
+ ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ mImeRequesterStatsToken = statsToken;
if (targetChanged) {
// target changed, check if new target can show IME.
ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
@@ -151,15 +194,20 @@
ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
? mImeRequester : mImeRequester.getWindow().getName());
mShowImeRunner = () -> {
+ ImeTracker.get().onProgress(mImeRequesterStatsToken,
+ ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
// Target should still be the same.
if (isReadyToShowIme()) {
+ ImeTracker.get().onProgress(mImeRequesterStatsToken,
+ ImeTracker.PHASE_WM_SHOW_IME_READY);
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
target.getWindow() != null ? target.getWindow().getName() : "");
setImeShowing(true);
- target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
+ target.showInsets(WindowInsets.Type.ime(), true /* fromIme */,
+ mImeRequesterStatsToken);
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
if (target != mImeRequester && mImeRequester != null) {
ProtoLog.w(WM_DEBUG_IME,
@@ -167,7 +215,12 @@
(mImeRequester.getWindow() != null
? mImeRequester.getWindow().getName() : ""));
}
+ } else {
+ ImeTracker.get().onFailed(mImeRequesterStatsToken,
+ ImeTracker.PHASE_WM_SHOW_IME_READY);
}
+ // Clear token here so we don't report an error in abortShowImePostLayout().
+ mImeRequesterStatsToken = null;
abortShowImePostLayout();
};
mDisplayContent.mWmService.requestTraversal();
@@ -202,6 +255,8 @@
mImeRequester = null;
mIsImeLayoutDrawn = false;
mShowImeRunner = null;
+ ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ mImeRequesterStatsToken = null;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 4c18d0b..56edde0 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -57,7 +57,6 @@
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.Button;
@@ -109,18 +108,13 @@
mContext = display.getDisplayId() == DEFAULT_DISPLAY
? uiContext : uiContext.createDisplayContext(display);
mHandler = new H(looper);
- mShowDelayMs = getNavBarExitDuration() * 3;
+ mShowDelayMs = context.getResources().getInteger(R.integer.dock_enter_exit_duration) * 3L;
mPanicThresholdMs = context.getResources()
.getInteger(R.integer.config_immersive_mode_confirmation_panic);
mVrModeEnabled = vrModeEnabled;
mCanSystemBarsBeShownByUser = canSystemBarsBeShownByUser;
}
- private long getNavBarExitDuration() {
- Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
- return exit != null ? exit.getDuration() : 0;
- }
-
static boolean loadSetting(int currentUserId, Context context) {
final boolean wasConfirmed = sConfirmed;
sConfirmed = false;
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index d35b7c3..8ecbc17 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,9 +16,11 @@
package com.android.server.wm;
+import android.annotation.Nullable;
import android.inputmethodservice.InputMethodService;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
/**
* Generalization of an object that can control insets state.
@@ -57,8 +59,10 @@
*
* @param types to specify which types of insets source window should be shown.
* @param fromIme {@code true} if IME show request originated from {@link InputMethodService}.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- default void showInsets(@InsetsType int types, boolean fromIme) {
+ default void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
/**
@@ -66,8 +70,10 @@
*
* @param types to specify which types of insets source window should be hidden.
* @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
- default void hideInsets(@InsetsType int types, boolean fromIme) {
+ default void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7e56dbf..67cab10 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -26,6 +26,7 @@
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
@@ -288,8 +289,9 @@
final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
// Always use windowing mode fullscreen when get insets for window metrics to make sure it
// contains all insets types.
- final InsetsState originalState = enforceInsetsPolicyForTarget(WINDOWING_MODE_FULLSCREEN,
- alwaysOnTop, attrs, mStateController.getRawInsetsState());
+ final InsetsState originalState = mDisplayContent.getInsetsPolicy()
+ .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop,
+ attrs.type, mStateController.getRawInsetsState());
InsetsState state = adjustVisibilityForTransientTypes(originalState);
return adjustInsetsForRoundedCorners(token, state, state == originalState);
}
@@ -341,42 +343,56 @@
/**
- * Modifies the given {@code state} according to the target's window state.
- * When performing layout of the target or dispatching insets to the target, we need to adjust
- * sources based on the target. e.g., the floating window will not receive system bars other
- * than caption, and some insets provider may request to override sizes for given window types.
- * Since the window type and the insets types provided by the window shall not change at
- * runtime, rotation doesn't matter in the layout params.
+ * Modifies the given {@code state} according to the {@code type} (Inset type) provided by
+ * the target.
+ * When performing layout of the target or dispatching insets to the target, we need to exclude
+ * sources which should not be visible to the target. e.g., the source which represents the
+ * target window itself, and the IME source when the target is above IME. We also need to
+ * exclude certain types of insets source for client within specific windowing modes.
*
+ * @param type the inset type provided by the target
* @param windowingMode the windowing mode of the target
* @param isAlwaysOnTop is the target always on top
- * @param attrs the layout params of the target
+ * @param windowType the type of the target
* @param state the input inset state containing all the sources
* @return The state stripped of the necessary information.
*/
- InsetsState enforceInsetsPolicyForTarget(@WindowConfiguration.WindowingMode int windowingMode,
- boolean isAlwaysOnTop, WindowManager.LayoutParams attrs, InsetsState state) {
+ InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type,
+ @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
+ int windowType, InsetsState state) {
boolean stateCopied = false;
- if (attrs.providedInsets != null && attrs.providedInsets.length > 0) {
+ if (type != ITYPE_INVALID) {
state = new InsetsState(state);
stateCopied = true;
- for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
- state.removeSource(attrs.providedInsets[i].type);
+ state.removeSource(type);
+
+ // Navigation bar doesn't get influenced by anything else
+ if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
+ state.removeSource(ITYPE_STATUS_BAR);
+ state.removeSource(ITYPE_CLIMATE_BAR);
+ state.removeSource(ITYPE_CAPTION_BAR);
+ state.removeSource(ITYPE_NAVIGATION_BAR);
+ state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ }
+
+ // Status bar doesn't get influenced by caption bar
+ if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
+ state.removeSource(ITYPE_CAPTION_BAR);
}
}
ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
.getSourceProviders();
for (int i = providers.size() - 1; i >= 0; i--) {
WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
- if (otherProvider.overridesFrame(attrs.type)) {
+ if (otherProvider.overridesFrame(windowType)) {
if (!stateCopied) {
state = new InsetsState(state);
stateCopied = true;
}
InsetsSource override =
new InsetsSource(state.getSource(otherProvider.getSource().getType()));
- override.setFrame(otherProvider.getOverriddenFrame(attrs.type));
+ override.setFrame(otherProvider.getOverriddenFrame(windowType));
state.addSource(override);
}
}
@@ -438,8 +454,7 @@
final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
if (originalImeSource != null) {
- final boolean imeVisibility =
- w.mActivityRecord.mLastImeShown || w.isRequestedVisible(Type.ime());
+ final boolean imeVisibility = w.isRequestedVisible(Type.ime());
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
final InsetsSource imeSource = new InsetsSource(originalImeSource);
@@ -783,7 +798,7 @@
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
- null /* translator */);
+ null /* translator */, null /* statsToken */);
SurfaceAnimationThread.getHandler().post(
() -> mListener.onReady(mAnimationControl, typesReady));
}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index a0e22e7..65298c8 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -26,6 +26,7 @@
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.util.Size;
+import android.view.View;
/**
* The static class that defines some utility constants and functions that are shared among launch
@@ -43,6 +44,8 @@
private static final int DEFAULT_LANDSCAPE_FREEFORM_WIDTH_DP = 1064;
private static final int DEFAULT_LANDSCAPE_FREEFORM_HEIGHT_DP = 600;
+ private static final int DISPLAY_EDGE_OFFSET_DP = 27;
+
private LaunchParamsUtil() {}
/**
@@ -126,4 +129,44 @@
return new Size(adjWidth, adjHeight);
}
+
+ static void adjustBoundsToFitInDisplayArea(@NonNull Rect stableBounds, int layoutDirection,
+ @NonNull ActivityInfo.WindowLayout layout,
+ @NonNull Rect inOutBounds) {
+ if (stableBounds.width() < inOutBounds.width()
+ || stableBounds.height() < inOutBounds.height()) {
+ // There is no way for us to fit the bounds in the displayArea without changing width
+ // or height. Just move the start to align with the displayArea.
+ final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
+ ? stableBounds.right - inOutBounds.right + inOutBounds.left
+ : stableBounds.left;
+ inOutBounds.offsetTo(left, stableBounds.top);
+ return;
+ }
+
+ final int dx;
+ if (inOutBounds.right > stableBounds.right) {
+ // Right edge is out of displayArea.
+ dx = stableBounds.right - inOutBounds.right;
+ } else if (inOutBounds.left < stableBounds.left) {
+ // Left edge is out of displayArea.
+ dx = stableBounds.left - inOutBounds.left;
+ } else {
+ // Vertical edges are all in displayArea.
+ dx = 0;
+ }
+
+ final int dy;
+ if (inOutBounds.top < stableBounds.top) {
+ // Top edge is out of displayArea.
+ dy = stableBounds.top - inOutBounds.top;
+ } else if (inOutBounds.bottom > stableBounds.bottom) {
+ // Bottom edge is out of displayArea.
+ dy = stableBounds.bottom - inOutBounds.bottom;
+ } else {
+ // Horizontal edges are all in displayArea.
+ dy = 0;
+ }
+ inOutBounds.offset(dx, dy);
+ }
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ea82417..2dbccae 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -501,12 +501,16 @@
if (hasVisibleTaskbar(mainWindow)) {
cropBounds = new Rect(mActivityRecord.getBounds());
+
+ // Rounded corners should be displayed above the taskbar.
+ // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo
+ // because taskbar bounds are in screen coordinates
+ adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
+
// Activity bounds are in screen coordinates while (0,0) for activity's surface
// control is at the top left corner of an app window so offsetting bounds
// accordingly.
cropBounds.offsetTo(0, 0);
- // Rounded corners should be displayed above the taskbar.
- adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
}
transaction
@@ -576,9 +580,8 @@
// Rounded corners should be displayed above the taskbar.
bounds.bottom =
Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
- if (mActivityRecord.inSizeCompatMode()
- && mActivityRecord.getSizeCompatScale() < 1.0f) {
- bounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+ if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) {
+ bounds.scale(1.0f / mActivityRecord.getCompatScale());
}
}
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index ccc71bb..de42c55 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -16,15 +16,21 @@
package com.android.server.wm;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
+
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
+import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.Display.Mode;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.SurfaceControl.RefreshRateRange;
import java.util.HashMap;
+import java.util.Objects;
/**
* Policy to select a lower refresh rate for the display if applicable.
@@ -154,39 +160,109 @@
return LAYER_PRIORITY_UNSET;
}
- float getPreferredRefreshRate(WindowState w) {
+ public static class FrameRateVote {
+ float mRefreshRate;
+ @Surface.FrameRateCompatibility int mCompatibility;
+
+ FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+ update(refreshRate, compatibility);
+ }
+
+ FrameRateVote() {
+ reset();
+ }
+
+ boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+ if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+ mRefreshRate = refreshRate;
+ mCompatibility = compatibility;
+ return true;
+ }
+ return false;
+ }
+
+ boolean reset() {
+ return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof FrameRateVote)) {
+ return false;
+ }
+
+ FrameRateVote other = (FrameRateVote) o;
+ return refreshRateEquals(other.mRefreshRate)
+ && mCompatibility == other.mCompatibility;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRefreshRate, mCompatibility);
+ }
+
+ @Override
+ public String toString() {
+ return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+ }
+
+ private boolean refreshRateEquals(float refreshRate) {
+ return mRefreshRate <= refreshRate + RefreshRateRange.FLOAT_TOLERANCE
+ && mRefreshRate >= refreshRate - RefreshRateRange.FLOAT_TOLERANCE;
+ }
+ }
+
+ boolean updateFrameRateVote(WindowState w) {
+ @DisplayManager.SwitchingType int refreshRateSwitchingType =
+ mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
+
+ // If refresh rate switching is disabled there is no point to set the frame rate on the
+ // surface as the refresh rate will be limited by display manager to a single value
+ // and SurfaceFlinger wouldn't be able to change it anyways.
+ if (refreshRateSwitchingType == SWITCHING_TYPE_NONE) {
+ return w.mFrameRateVote.reset();
+ }
+
// If app is animating, it's not able to control refresh rate because we want the animation
// to run in default refresh rate.
if (w.isAnimating(TRANSITION | PARENTS)) {
- return 0;
+ return w.mFrameRateVote.reset();
}
// If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
// of that mode id.
- final int preferredModeId = w.mAttrs.preferredDisplayModeId;
- if (preferredModeId > 0) {
- DisplayInfo info = w.getDisplayInfo();
- if (info != null) {
- for (Display.Mode mode : info.supportedModes) {
- if (preferredModeId == mode.getModeId()) {
- return mode.getRefreshRate();
+ if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+ final int preferredModeId = w.mAttrs.preferredDisplayModeId;
+ if (preferredModeId > 0) {
+ DisplayInfo info = w.getDisplayInfo();
+ if (info != null) {
+ for (Display.Mode mode : info.supportedModes) {
+ if (preferredModeId == mode.getModeId()) {
+ return w.mFrameRateVote.update(mode.getRefreshRate(),
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+
+ }
}
}
}
}
if (w.mAttrs.preferredRefreshRate > 0) {
- return w.mAttrs.preferredRefreshRate;
+ return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
+ Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
}
// If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
// list, we return the low refresh rate as the preferred one.
- final String packageName = w.getOwningPackage();
- if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
- return mLowRefreshRateMode.getRefreshRate();
+ if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+ final String packageName = w.getOwningPackage();
+ if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
+ return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ }
}
- return 0;
+ return w.mFrameRateVote.reset();
}
float getPreferredMinRefreshRate(WindowState w) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0ed4835..d53ee1e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -149,6 +149,7 @@
import com.android.server.am.UserState;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1503,7 +1504,8 @@
}
if (aInfo == null) {
- Slog.wtf(TAG, "No home screen found for " + homeIntent, new Throwable());
+ Slogf.wtf(TAG, new Exception(), "No home screen found for %s and user %d", homeIntent,
+ userId);
return null;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d06f271..b290bec 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -67,7 +67,6 @@
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -607,12 +606,6 @@
boolean mLastSurfaceShowing = true;
- /**
- * Tracks if a back gesture is in progress.
- * Skips any system transition animations if this is set to {@code true}.
- */
- boolean mBackGestureStarted = false;
-
private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -680,7 +673,7 @@
mLaunchCookie = _launchCookie;
mDeferTaskAppear = _deferTaskAppear;
mRemoveWithTaskOrganizer = _removeWithTaskOrganizer;
- EventLogTags.writeWmTaskCreated(mTaskId, isRootTask() ? INVALID_TASK_ID : getRootTaskId());
+ EventLogTags.writeWmTaskCreated(mTaskId);
}
static Task fromWindowContainerToken(WindowContainerToken token) {
@@ -1297,7 +1290,8 @@
}
void updateTaskMovement(boolean toTop, int position) {
- EventLogTags.writeWmTaskMoved(mTaskId, toTop ? 1 : 0, position);
+ EventLogTags.writeWmTaskMoved(mTaskId, getRootTaskId(), getDisplayId(), toTop ? 1 : 0,
+ position);
final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (taskDisplayArea != null && isLeafTask()) {
taskDisplayArea.onLeafTaskMoved(this, toTop);
@@ -1407,13 +1401,26 @@
* Reorder the history task so that the passed activity is brought to the front.
* @return whether it was actually moved (vs already being top).
*/
- final boolean moveActivityToFrontLocked(ActivityRecord newTop) {
+ final boolean moveActivityToFront(ActivityRecord newTop) {
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing and adding activity %s to root task at top "
+ "callers=%s", newTop, Debug.getCallers(4));
- int origDist = getDistanceFromTop(newTop);
- positionChildAtTop(newTop);
+ final TaskFragment taskFragment = newTop.getTaskFragment();
+ boolean moved;
+ if (taskFragment != this) {
+ if (taskFragment.isEmbedded() && taskFragment.getNonFinishingActivityCount() == 1) {
+ taskFragment.mClearedForReorderActivityToFront = true;
+ }
+ newTop.reparent(this, POSITION_TOP);
+ moved = true;
+ if (taskFragment.isEmbedded()) {
+ mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+ .onActivityReparentedToTask(newTop);
+ }
+ } else {
+ moved = moveChildToFront(newTop);
+ }
updateEffectiveIntent();
- return getDistanceFromTop(newTop) != origDist;
+ return moved;
}
@Override
@@ -1581,6 +1588,11 @@
removeChild(r, reason);
});
} else {
+ // Finish or destroy apps from the bottom to ensure that all the other activity have
+ // been finished and the top task in another task gets resumed when a top activity is
+ // removed. Otherwise, shell transitions wouldn't run because there would be no event
+ // that sets the transition ready.
+ final boolean traverseTopToBottom = !mTransitionController.isShellTransitionsEnabled();
forAllActivities((r) -> {
if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
return;
@@ -1594,7 +1606,7 @@
} else {
r.destroyIfPossible(reason);
}
- });
+ }, traverseTopToBottom);
}
}
@@ -2560,7 +2572,7 @@
}
mRemoving = true;
- EventLogTags.writeWmTaskRemoved(mTaskId, reason);
+ EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(), reason);
clearPinnedTaskIfNeed();
// If applicable let the TaskOrganizer know the Task is vanishing.
setTaskOrganizer(null);
@@ -2573,7 +2585,8 @@
void reparent(Task rootTask, int position, boolean moveParents, String reason) {
if (DEBUG_ROOT_TASK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
+ " from rootTask=" + getRootTask());
- EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason);
+ EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(),
+ "reParentTask:" + reason);
reparent(rootTask, position);
@@ -3080,20 +3093,6 @@
});
}
- void positionChildAtTop(ActivityRecord child) {
- positionChildAt(child, POSITION_TOP);
- }
-
- void positionChildAt(ActivityRecord child, int position) {
- if (child == null) {
- Slog.w(TAG_WM,
- "Attempted to position of non-existing app");
- return;
- }
-
- positionChildAt(position, child, false /* includeParents */);
- }
-
void setTaskDescription(TaskDescription taskDescription) {
mTaskDescription = taskDescription;
}
@@ -3331,14 +3330,6 @@
}
});
}
- } else if (mBackGestureStarted) {
- // Cancel playing transitions if a back navigation animation is in progress.
- // This bit is set by {@link BackNavigationController} when a back gesture is started.
- // It is used as a one-off transition overwrite that is cleared when the back gesture
- // is committed and triggers a transition, or when the gesture is cancelled.
- mBackGestureStarted = false;
- mDisplayContent.mSkipAppTransitionAnimation = true;
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Skipping app transition animation. task=%s", this);
} else {
super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
}
@@ -4304,13 +4295,14 @@
}
/**
- * @return true if the task is currently focused.
+ * @return {@code true} if the task is currently focused or one of its children is focused.
*/
boolean isFocused() {
if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
return false;
}
- return mDisplayContent.mFocusedApp.getTask() == this;
+ final Task focusedTask = mDisplayContent.mFocusedApp.getTask();
+ return focusedTask == this || (focusedTask != null && focusedTask.getParent() == this);
}
/**
@@ -4330,6 +4322,8 @@
*/
void onAppFocusChanged(boolean hasFocus) {
dispatchTaskInfoChangedIfNeeded(false /* force */);
+ final Task parentTask = getParent().asTask();
+ if (parentTask != null) parentTask.dispatchTaskInfoChangedIfNeeded(false /* force */);
}
void onPictureInPictureParamsChanged() {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index b6c14bb..6ff91af 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -455,7 +455,7 @@
}
mLastLeafTaskToFrontId = t.mTaskId;
- EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId);
+ EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId, getDisplayId());
// Notifying only when a leaf task moved to front. Or the listeners would be notified
// couple times from the leaf task all the way up to the root task.
mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(t.getTaskInfo());
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index ae1e3e7..f0e3644 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -224,6 +224,14 @@
private TaskFragment mAdjacentTaskFragment;
/**
+ * Unlike the {@link mAdjacentTaskFragment}, the companion TaskFragment is not always visually
+ * adjacent to this one, but this TaskFragment will be removed by the organizer if the
+ * companion TaskFragment is removed.
+ */
+ @Nullable
+ private TaskFragment mCompanionTaskFragment;
+
+ /**
* Prevents duplicate calls to onTaskAppeared.
*/
boolean mTaskFragmentAppearedSent;
@@ -241,6 +249,12 @@
boolean mClearedTaskFragmentForPip;
/**
+ * The last running activity of the TaskFragment was removed and added to the top-most of the
+ * Task because it was launched with FLAG_ACTIVITY_REORDER_TO_FRONT.
+ */
+ boolean mClearedForReorderActivityToFront;
+
+ /**
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*
@@ -291,6 +305,12 @@
private final IBinder mFragmentToken;
/**
+ * Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
+ * configuration change.
+ */
+ private boolean mDelayOrganizedTaskFragmentSurfaceUpdate;
+
+ /**
* Whether to delay the last activity of TaskFragment being immediately removed while finishing.
* This should only be set on a embedded TaskFragment, where the organizer can have the
* opportunity to perform animations and finishing the adjacent TaskFragment.
@@ -390,6 +410,14 @@
}
}
+ void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment) {
+ mCompanionTaskFragment = companionTaskFragment;
+ }
+
+ TaskFragment getCompanionTaskFragment() {
+ return mCompanionTaskFragment;
+ }
+
void resetAdjacentTaskFragment() {
// Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
@@ -555,15 +583,7 @@
@Override
boolean isEmbedded() {
- if (mIsEmbedded) {
- return true;
- }
- final WindowContainer<?> parent = getParent();
- if (parent != null) {
- final TaskFragment taskFragment = parent.asTaskFragment();
- return taskFragment != null && taskFragment.isEmbedded();
- }
- return false;
+ return mIsEmbedded;
}
@EmbeddingCheckResult
@@ -1846,6 +1866,7 @@
ActivityRecord r = topRunningActivity();
mClearedTaskForReuse = false;
mClearedTaskFragmentForPip = false;
+ mClearedForReorderActivityToFront = false;
final ActivityRecord addingActivity = child.asActivityRecord();
final boolean isAddingActivity = addingActivity != null;
@@ -2264,35 +2285,41 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
- // Task will animate differently.
- if (mTaskFragmentOrganizer != null) {
- mTmpPrevBounds.set(getBounds());
- }
-
super.onConfigurationChanged(newParentConfig);
- final boolean shouldStartChangeTransition = shouldStartChangeTransition(mTmpPrevBounds);
- if (shouldStartChangeTransition) {
- initializeChangeTransition(mTmpPrevBounds);
- }
if (mTaskFragmentOrganizer != null) {
- if (mTransitionController.isShellTransitionsEnabled()
- && !mTransitionController.isCollecting(this)) {
- // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
- // update the surface here if it is not collected by Shell transition.
- updateOrganizedTaskFragmentSurface();
- } else if (!mTransitionController.isShellTransitionsEnabled()
- && !shouldStartChangeTransition) {
- // Update the surface here instead of in the organizer so that we can make sure
- // it can be synced with the surface freezer for legacy app transition.
- updateOrganizedTaskFragmentSurface();
- }
+ updateOrganizedTaskFragmentSurface();
}
sendTaskFragmentInfoChanged();
}
+ void deferOrganizedTaskFragmentSurfaceUpdate() {
+ mDelayOrganizedTaskFragmentSurfaceUpdate = true;
+ }
+
+ void continueOrganizedTaskFragmentSurfaceUpdate() {
+ mDelayOrganizedTaskFragmentSurfaceUpdate = false;
+ updateOrganizedTaskFragmentSurface();
+ }
+
private void updateOrganizedTaskFragmentSurface() {
+ if (mDelayOrganizedTaskFragmentSurfaceUpdate) {
+ return;
+ }
+ if (mTransitionController.isShellTransitionsEnabled()
+ && !mTransitionController.isCollecting(this)) {
+ // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
+ // update the surface here if it is not collected by Shell transition.
+ updateOrganizedTaskFragmentSurfaceUnchecked();
+ } else if (!mTransitionController.isShellTransitionsEnabled() && !isAnimating()) {
+ // Update the surface here instead of in the organizer so that we can make sure
+ // it can be synced with the surface freezer for legacy app transition.
+ updateOrganizedTaskFragmentSurfaceUnchecked();
+ }
+ }
+
+ private void updateOrganizedTaskFragmentSurfaceUnchecked() {
final SurfaceControl.Transaction t = getSyncTransaction();
updateSurfacePosition(t);
updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
@@ -2327,6 +2354,11 @@
if (mTaskFragmentOrganizer != null
&& (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
t.setWindowCrop(mSurfaceControl, 0, 0);
+ final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
+ if (t != syncTransaction) {
+ // Avoid restoring to old window crop if the sync transaction is applied later.
+ syncTransaction.setWindowCrop(mSurfaceControl, 0, 0);
+ }
mLastSurfaceSize.set(0, 0);
}
}
@@ -2341,7 +2373,7 @@
}
/** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
- private boolean shouldStartChangeTransition(Rect startBounds) {
+ boolean shouldStartChangeTransition(Rect startBounds) {
if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
return false;
}
@@ -2361,7 +2393,7 @@
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
if (mTaskFragmentOrganizer != null) {
- updateOrganizedTaskFragmentSurface();
+ updateOrganizedTaskFragmentSurfaceUnchecked();
// If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
// emit the callbacks now.
sendTaskFragmentAppeared();
@@ -2423,6 +2455,7 @@
positionInParent,
mClearedTaskForReuse,
mClearedTaskFragmentForPip,
+ mClearedForReorderActivityToFront,
calculateMinDimension());
}
@@ -2469,6 +2502,22 @@
return mTaskFragmentOrganizer != null;
}
+ /**
+ * Whether this is an embedded {@link TaskFragment} that does not fill the parent {@link Task}.
+ */
+ boolean isEmbeddedWithBoundsOverride() {
+ if (!mIsEmbedded) {
+ return false;
+ }
+ final Task task = getTask();
+ if (task == null) {
+ return false;
+ }
+ final Rect taskBounds = task.getBounds();
+ final Rect taskFragBounds = getBounds();
+ return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+ }
+
/** Whether the Task should be visible. */
boolean isTaskVisibleRequested() {
final Task task = getTask();
@@ -2709,6 +2758,16 @@
return callback.test(this) ? this : null;
}
+ /**
+ * Moves the passed child to front
+ * @return whether it was actually moved (vs already being top).
+ */
+ boolean moveChildToFront(WindowContainer newTop) {
+ int origDist = getDistanceFromTop(newTop);
+ positionChildAt(POSITION_TOP, newTop, false /* includeParents */);
+ return getDistanceFromTop(newTop) != origDist;
+ }
+
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 509b1e6..6e4df79 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -34,8 +34,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Intent;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -132,12 +132,11 @@
new WeakHashMap<>();
/**
- * Map from Task Id to {@link RemoteAnimationDefinition}.
- * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
- * RemoteAnimationDefinition) )
+ * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
+ * organized by this organizer.
*/
- private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
- new SparseArray<>();
+ @Nullable
+ private RemoteAnimationDefinition mRemoteAnimationDefinition;
/**
* Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
@@ -322,9 +321,10 @@
+ " is not in a task belong to the organizer app.");
return null;
}
- if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
+ if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
+ || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
Slog.d(TAG, "Reparent activity=" + activity.token
- + " is not allowed to be embedded.");
+ + " is not allowed to be embedded in trusted mode.");
return null;
}
@@ -350,7 +350,7 @@
activity.token, task.mTaskId);
return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
.setTaskId(task.mTaskId)
- .setActivityIntent(activity.intent)
+ .setActivityIntent(trimIntent(activity.intent))
.setActivityToken(activityToken);
}
@@ -425,7 +425,7 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Register task fragment organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
- if (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+ if (isOrganizerRegistered(organizer)) {
throw new IllegalStateException(
"Replacing existing organizer currently unsupported");
}
@@ -453,7 +453,7 @@
}
@Override
- public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId,
+ public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
@NonNull RemoteAnimationDefinition definition) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -466,20 +466,19 @@
if (organizerState == null) {
throw new IllegalStateException("The organizer hasn't been registered.");
}
- if (organizerState.mRemoteAnimationDefinitions.contains(taskId)) {
+ if (organizerState.mRemoteAnimationDefinition != null) {
throw new IllegalStateException(
"The organizer has already registered remote animations="
- + organizerState.mRemoteAnimationDefinitions.get(taskId)
- + " for TaskId=" + taskId);
+ + organizerState.mRemoteAnimationDefinition);
}
definition.setCallingPidUid(pid, uid);
- organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
+ organizerState.mRemoteAnimationDefinition = definition;
}
}
@Override
- public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) {
+ public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
@@ -493,7 +492,7 @@
return;
}
- organizerState.mRemoteAnimationDefinitions.remove(taskId);
+ organizerState.mRemoteAnimationDefinition = null;
}
}
@@ -503,10 +502,18 @@
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
- applyTransaction(wct, transitionType, shouldApplyIndependently);
- final TaskFragmentOrganizerState state = validateAndGetState(
- wct.getTaskFragmentOrganizer());
- state.onTransactionFinished(transactionToken);
+ if (isValidTransaction(wct)) {
+ applyTransaction(wct, transitionType, shouldApplyIndependently);
+ }
+ // Even if the transaction is empty, we still need to invoke #onTransactionFinished
+ // unless the organizer has been unregistered.
+ final ITaskFragmentOrganizer organizer = wct.getTaskFragmentOrganizer();
+ final TaskFragmentOrganizerState state = organizer != null
+ ? mTaskFragmentOrganizerState.get(organizer.asBinder())
+ : null;
+ if (state != null) {
+ state.onTransactionFinished(transactionToken);
+ }
}
}
@@ -515,7 +522,7 @@
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
- if (wct.isEmpty()) {
+ if (!isValidTransaction(wct)) {
return;
}
mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
@@ -525,16 +532,16 @@
/**
* Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
- * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
+ * {@code null} if it doesn't.
*/
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
- @NonNull ITaskFragmentOrganizer organizer, int taskId) {
+ @NonNull ITaskFragmentOrganizer organizer) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
return organizerState != null
- ? organizerState.mRemoteAnimationDefinitions.get(taskId)
+ ? organizerState.mRemoteAnimationDefinition
: null;
}
}
@@ -656,7 +663,7 @@
}
organizer = organizedTf[0].getTaskFragmentOrganizer();
}
- if (!mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+ if (!isOrganizerRegistered(organizer)) {
Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
return;
}
@@ -702,7 +709,7 @@
mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).remove(event);
}
- boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
+ private boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
}
@@ -739,6 +746,20 @@
return state;
}
+ boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
+ if (t.isEmpty()) {
+ return false;
+ }
+ final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
+ if (t.getTaskFragmentOrganizer() == null || !isOrganizerRegistered(organizer)) {
+ // Transaction from an unregistered organizer should not be applied. This can happen
+ // when the organizer process died before the transaction is applied.
+ Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
+ return false;
+ }
+ return true;
+ }
+
/**
* A class to store {@link ITaskFragmentOrganizer} and its organized
* {@link TaskFragment TaskFragments} with different pending event request.
@@ -1083,16 +1104,18 @@
return false;
}
final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
- if (taskFragment == null) {
- return false;
- }
- final Task parentTask = taskFragment.getTask();
- if (parentTask != null) {
- final Rect taskBounds = parentTask.getBounds();
- final Rect taskFragBounds = taskFragment.getBounds();
- return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
- }
- return false;
+ return taskFragment != null && taskFragment.isEmbeddedWithBoundsOverride();
}
}
+
+ /**
+ * Trims the given Intent to only those that are needed to for embedding rules. This helps to
+ * make it safer for cross-uid embedding even if we only send the Intent for trusted embedding.
+ */
+ private static Intent trimIntent(@NonNull Intent intent) {
+ return new Intent()
+ .setComponent(intent.getComponent())
+ .setPackage(intent.getPackage())
+ .setAction(intent.getAction());
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 8444489..fdbed2a 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -51,7 +51,6 @@
import android.util.Size;
import android.util.Slog;
import android.view.Gravity;
-import android.view.View;
import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
@@ -182,26 +181,34 @@
// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
// different, we should recalculating the bounds.
boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false;
- final boolean canApplyFreeformPolicy =
+ // Note that initial bounds needs to be set to fullscreen tasks too as it's used as restore
+ // bounds.
+ final boolean canCalculateBoundsForFullscreenTask =
+ canCalculateBoundsForFullscreenTask(suggestedDisplayArea, launchMode);
+ final boolean canApplyFreeformWindowPolicy =
canApplyFreeformWindowPolicy(suggestedDisplayArea, launchMode);
- if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
- && (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) {
+ final boolean canApplyWindowLayout = layout != null
+ && (canApplyFreeformWindowPolicy || canCalculateBoundsForFullscreenTask);
+ final boolean canApplyBoundsFromActivityOptions =
+ mSupervisor.canUseActivityOptionsLaunchBounds(options)
+ && (canApplyFreeformWindowPolicy
+ || canApplyPipWindowPolicy(launchMode)
+ || canCalculateBoundsForFullscreenTask);
+
+ if (canApplyBoundsFromActivityOptions) {
hasInitialBounds = true;
- launchMode = launchMode == WINDOWING_MODE_UNDEFINED
+ // |launchMode| at this point can be fullscreen, PIP, MultiWindow, etc. Only set
+ // freeform windowing mode if appropriate by checking |canApplyFreeformWindowPolicy|.
+ launchMode = launchMode == WINDOWING_MODE_UNDEFINED && canApplyFreeformWindowPolicy
? WINDOWING_MODE_FREEFORM
: launchMode;
outParams.mBounds.set(options.getLaunchBounds());
if (DEBUG) appendLog("activity-options-bounds=" + outParams.mBounds);
- } else if (launchMode == WINDOWING_MODE_PINNED) {
- // System controls PIP window's bounds, so don't apply launch bounds.
- if (DEBUG) appendLog("empty-window-layout-for-pip");
- } else if (launchMode == WINDOWING_MODE_FULLSCREEN) {
- if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds);
- } else if (layout != null && canApplyFreeformPolicy) {
+ } else if (canApplyWindowLayout) {
mTmpBounds.set(currentParams.mBounds);
getLayoutBounds(suggestedDisplayArea, root, layout, mTmpBounds);
if (!mTmpBounds.isEmpty()) {
- launchMode = WINDOWING_MODE_FREEFORM;
+ launchMode = canApplyFreeformWindowPolicy ? WINDOWING_MODE_FREEFORM : launchMode;
outParams.mBounds.set(mTmpBounds);
hasInitialBounds = true;
hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = true;
@@ -211,6 +218,8 @@
}
} else if (launchMode == WINDOWING_MODE_MULTI_WINDOW
&& options != null && options.getLaunchBounds() != null) {
+ // TODO: Investigate whether we can migrate this clause to the
+ // |canApplyBoundsFromActivityOptions| case above.
outParams.mBounds.set(options.getLaunchBounds());
hasInitialBounds = true;
if (DEBUG) appendLog("multiwindow-activity-options-bounds=" + outParams.mBounds);
@@ -250,11 +259,9 @@
if (!currentParams.mBounds.isEmpty()) {
// Carry over bounds from callers regardless of launch mode because bounds is still
// used to restore last non-fullscreen bounds when launch mode is not freeform.
- // Therefore it's not a resolution step for non-freeform launch mode and only
- // consider it fully resolved only when launch mode is freeform.
outParams.mBounds.set(currentParams.mBounds);
+ fullyResolvedCurrentParam = true;
if (launchMode == WINDOWING_MODE_FREEFORM) {
- fullyResolvedCurrentParam = true;
if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);
}
}
@@ -364,13 +371,13 @@
if (resolvedMode == WINDOWING_MODE_FREEFORM) {
// Make sure bounds are in the displayArea.
if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) {
- adjustBoundsToFitInDisplayArea(taskDisplayArea, outParams.mBounds);
+ adjustBoundsToFitInDisplayArea(taskDisplayArea, layout, outParams.mBounds);
}
// Even though we want to keep original bounds, we still don't want it to stomp on
// an existing task.
adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds);
}
- } else if (taskDisplayArea.inFreeformWindowingMode()) {
+ } else {
if (source != null && source.inFreeformWindowingMode()
&& resolvedMode == WINDOWING_MODE_FREEFORM
&& outParams.mBounds.isEmpty()
@@ -562,10 +569,19 @@
return display.getDisplayId() == source.getDisplayId();
}
+ private boolean canCalculateBoundsForFullscreenTask(@NonNull TaskDisplayArea displayArea,
+ int launchMode) {
+ return mSupervisor.mService.mSupportsFreeformWindowManagement
+ && ((displayArea.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && launchMode == WINDOWING_MODE_UNDEFINED)
+ || launchMode == WINDOWING_MODE_FULLSCREEN);
+ }
+
private boolean canApplyFreeformWindowPolicy(@NonNull TaskDisplayArea suggestedDisplayArea,
int launchMode) {
return mSupervisor.mService.mSupportsFreeformWindowManagement
- && (suggestedDisplayArea.inFreeformWindowingMode()
+ && ((suggestedDisplayArea.inFreeformWindowingMode()
+ && launchMode == WINDOWING_MODE_UNDEFINED)
|| launchMode == WINDOWING_MODE_FREEFORM);
}
@@ -727,16 +743,10 @@
private void getTaskBounds(@NonNull ActivityRecord root, @NonNull TaskDisplayArea displayArea,
@NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds,
@NonNull Rect inOutBounds) {
- if (resolvedMode == WINDOWING_MODE_FULLSCREEN) {
- // We don't handle letterboxing here. Letterboxing will be handled by valid checks
- // later.
- inOutBounds.setEmpty();
- if (DEBUG) appendLog("maximized-bounds");
- return;
- }
-
- if (resolvedMode != WINDOWING_MODE_FREEFORM) {
- // We don't apply freeform bounds adjustment to other windowing modes.
+ if (resolvedMode != WINDOWING_MODE_FREEFORM
+ && resolvedMode != WINDOWING_MODE_FULLSCREEN) {
+ // This function should be used only for freeform bounds adjustment. Freeform bounds
+ // needs to be set to fullscreen tasks too as restore bounds.
if (DEBUG) {
appendLog("skip-bounds-" + WindowConfiguration.windowingModeToString(resolvedMode));
}
@@ -777,7 +787,7 @@
// we may need to move it back to the displayArea.
LaunchParamsUtil.centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(),
inOutBounds);
- adjustBoundsToFitInDisplayArea(displayArea, inOutBounds);
+ adjustBoundsToFitInDisplayArea(displayArea, layout, inOutBounds);
if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds);
}
@@ -824,47 +834,13 @@
}
private void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
- @NonNull Rect inOutBounds) {
- final Rect stableBounds = mTmpStableBounds;
- displayArea.getStableRect(stableBounds);
-
- if (stableBounds.width() < inOutBounds.width()
- || stableBounds.height() < inOutBounds.height()) {
- // There is no way for us to fit the bounds in the displayArea without changing width
- // or height. Just move the start to align with the displayArea.
- final int layoutDirection =
- mSupervisor.mRootWindowContainer.getConfiguration().getLayoutDirection();
- final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
- ? stableBounds.right - inOutBounds.right + inOutBounds.left
- : stableBounds.left;
- inOutBounds.offsetTo(left, stableBounds.top);
- return;
- }
-
- final int dx;
- if (inOutBounds.right > stableBounds.right) {
- // Right edge is out of displayArea.
- dx = stableBounds.right - inOutBounds.right;
- } else if (inOutBounds.left < stableBounds.left) {
- // Left edge is out of displayArea.
- dx = stableBounds.left - inOutBounds.left;
- } else {
- // Vertical edges are all in displayArea.
- dx = 0;
- }
-
- final int dy;
- if (inOutBounds.top < stableBounds.top) {
- // Top edge is out of displayArea.
- dy = stableBounds.top - inOutBounds.top;
- } else if (inOutBounds.bottom > stableBounds.bottom) {
- // Bottom edge is out of displayArea.
- dy = stableBounds.bottom - inOutBounds.bottom;
- } else {
- // Horizontal edges are all in displayArea.
- dy = 0;
- }
- inOutBounds.offset(dx, dy);
+ @NonNull ActivityInfo.WindowLayout layout,
+ @NonNull Rect inOutBounds) {
+ final int layoutDirection = mSupervisor.mRootWindowContainer.getConfiguration()
+ .getLayoutDirection();
+ displayArea.getStableRect(mTmpStableBounds);
+ LaunchParamsUtil.adjustBoundsToFitInDisplayArea(mTmpStableBounds, layoutDirection, layout,
+ inOutBounds);
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a64bd69..ef68590 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -928,10 +928,16 @@
mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
}
+ // Check whether the participants were animated from back navigation.
+ final boolean markBackAnimated = mController.mAtm.mBackNavigationController
+ .containsBackAnimationTargets(this);
// Resolve the animating targets from the participants
mTargets = calculateTargets(mParticipants, mChanges);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges,
transaction);
+ if (markBackAnimated) {
+ mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
+ }
if (mOverrideOptions != null) {
info.setAnimationOptions(mOverrideOptions);
if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
@@ -1935,9 +1941,20 @@
final Task task = wc.asTask();
if (task != null) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
- if (topActivity != null && topActivity.mStartingData != null
- && topActivity.mStartingData.hasImeSurface()) {
- flags |= FLAG_WILL_IME_SHOWN;
+ if (topActivity != null) {
+ if (topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ flags |= FLAG_WILL_IME_SHOWN;
+ }
+ if (topActivity.mAtmService.mBackNavigationController
+ .isMonitorTransitionTarget(topActivity)) {
+ flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+ }
+ } else {
+ if (task.mAtmService.mBackNavigationController
+ .isMonitorTransitionTarget(task)) {
+ flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+ }
}
if (task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
@@ -1951,6 +1968,10 @@
flags |= FLAG_IS_VOICE_INTERACTION;
}
flags |= record.mTransitionChangeFlags;
+ if (record.mAtmService.mBackNavigationController
+ .isMonitorTransitionTarget(record)) {
+ flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+ }
}
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && task == null) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 37bef3a..25df511 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -423,7 +423,7 @@
Transition newTransition = null;
if (isCollecting()) {
if (displayChange != null) {
- throw new IllegalArgumentException("Provided displayChange for a non-new request");
+ Slog.e(TAG, "Provided displayChange for a non-new request", new Throwable());
}
// Make the collecting transition wait until this request is ready.
mCollectingTransition.setReady(readyGroupRef, false);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 73d4496..05dea0e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3009,10 +3009,9 @@
// screen empty. Show background color to cover that.
showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
} else {
- // Check whether or not to show backdrop for open/close transition.
- final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
- final Animation a = animAttr != 0
- ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+ // Check whether the app has requested to show backdrop for open/close
+ // transition.
+ final Animation a = appTransition.getNextAppRequestedAnimation(enter);
showBackdrop = a != null && a.getShowBackdrop();
}
backdropColor = appTransition.getNextAppTransitionBackgroundColor();
@@ -3277,9 +3276,10 @@
void resetSurfacePositionForAnimationLeash(Transaction t) {
t.setPosition(mSurfaceControl, 0, 0);
- if (mSyncState != SYNC_STATE_NONE && t != mSyncTransaction) {
+ final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
+ if (t != syncTransaction) {
// Avoid restoring to old position if the sync transaction is applied later.
- mSyncTransaction.setPosition(mSurfaceControl, 0, 0);
+ syncTransaction.setPosition(mSurfaceControl, 0, 0);
}
mLastSurfacePosition.set(0, 0);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c206a15..1282acb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -43,6 +43,7 @@
import android.view.SurfaceControlViewHost;
import android.view.WindowInfo;
import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.input.InputManagerService;
@@ -729,16 +730,20 @@
* Show IME on imeTargetWindow once IME has finished layout.
*
* @param imeTargetWindowToken token of the (IME target) window on which IME should be shown.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- public abstract void showImePostLayout(IBinder imeTargetWindowToken);
+ public abstract void showImePostLayout(IBinder imeTargetWindowToken,
+ @Nullable ImeTracker.Token statsToken);
/**
* Hide IME using imeTargetWindow when requested.
*
* @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
* @param displayId the id of the display the IME is on.
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
- public abstract void hideIme(IBinder imeTargetWindowToken, int displayId);
+ public abstract void hideIme(IBinder imeTargetWindowToken, int displayId,
+ @Nullable ImeTracker.Token statsToken);
/**
* Tell window manager about a package that should be running with a restricted range of
@@ -898,4 +903,7 @@
* could not be prepared and the session needs to be torn down.
*/
public abstract boolean setContentRecordingSession(ContentRecordingSession incomingSession);
+
+ /** Returns the SurfaceControl accessibility services should use for accessibility overlays. */
+ public abstract SurfaceControl getA11yOverlayLayer(int displayId);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 848c231..1289634 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -292,6 +292,7 @@
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
@@ -735,7 +736,6 @@
@VisibleForTesting
final ContentRecordingController mContentRecordingController = new ContentRecordingController();
-
@VisibleForTesting
final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
@@ -1842,7 +1842,7 @@
// Make this invalid which indicates a null attached frame.
outAttachedFrame.set(0, 0, -1, -1);
}
- outSizeCompatScale[0] = win.getSizeCompatScale();
+ outSizeCompatScale[0] = win.getCompatScaleForClient();
}
Binder.restoreCallingIdentity(origId);
@@ -1928,22 +1928,14 @@
&& attachedWindow.mActivityRecord.mTargetSdk >= Build.VERSION_CODES.O;
} else {
// Otherwise, look at the package
- try {
- ApplicationInfo appInfo = mContext.getPackageManager()
- .getApplicationInfoAsUser(packageName, 0,
- UserHandle.getUserId(callingUid));
- if (appInfo.uid != callingUid) {
- throw new SecurityException("Package " + packageName + " not in UID "
- + callingUid);
- }
- if (appInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- /* ignore */
+ final ApplicationInfo appInfo = mPmInternal.getApplicationInfo(
+ packageName, 0 /* flags */, SYSTEM_UID, UserHandle.getUserId(callingUid));
+ if (appInfo == null || appInfo.uid != callingUid) {
+ throw new SecurityException("Package " + packageName + " not in UID "
+ + callingUid);
}
+ return appInfo.targetSdkVersion >= Build.VERSION_CODES.O;
}
- return false;
}
/**
@@ -5295,7 +5287,6 @@
public static final int WINDOW_FREEZE_TIMEOUT = 11;
public static final int PERSIST_ANIMATION_SCALE = 14;
- public static final int FORCE_GC = 15;
public static final int ENABLE_SCREEN = 16;
public static final int APP_FREEZE_TIMEOUT = 17;
public static final int REPORT_WINDOWS_CHANGE = 19;
@@ -5386,26 +5377,6 @@
break;
}
- case FORCE_GC: {
- synchronized (mGlobalLock) {
- // Since we're holding both mWindowMap and mAnimator we don't need to
- // hold mAnimator.mLayoutToAnim.
- if (mAnimator.isAnimationScheduled()) {
- // If we are animating, don't do the gc now but
- // delay a bit so we don't interrupt the animation.
- sendEmptyMessageDelayed(H.FORCE_GC, 2000);
- return;
- }
- // If we are currently rotating the display, it will
- // schedule a new message when done.
- if (mDisplayFrozen) {
- return;
- }
- }
- Runtime.getRuntime().gc();
- break;
- }
-
case ENABLE_SCREEN: {
performEnableScreen();
break;
@@ -6264,14 +6235,6 @@
// now to catch that.
configChanged = displayContent != null && displayContent.updateOrientation();
- // A little kludge: a lot could have happened while the
- // display was frozen, so now that we are coming back we
- // do a gc so that any remote references the system
- // processes holds on others can be released if they are
- // no longer needed.
- mH.removeMessages(H.FORCE_GC);
- mH.sendEmptyMessageDelayed(H.FORCE_GC, 2000);
-
mScreenFrozenLock.release();
if (updateRotation && displayContent != null) {
@@ -8051,7 +8014,8 @@
}
@Override
- public void showImePostLayout(IBinder imeTargetWindowToken) {
+ public void showImePostLayout(IBinder imeTargetWindowToken,
+ @Nullable ImeTracker.Token statsToken) {
synchronized (mGlobalLock) {
InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken);
if (imeTarget == null) {
@@ -8060,17 +8024,18 @@
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
imeTarget = controlTarget.getWindow();
- // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
- // is controlled by default display
+ // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget
+ // which is controlled by default display
final DisplayContent dc = imeTarget != null
? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
dc.getInsetsStateController().getImeSourceProvider()
- .scheduleShowImePostLayout(controlTarget);
+ .scheduleShowImePostLayout(controlTarget, statsToken);
}
}
@Override
- public void hideIme(IBinder imeTargetWindowToken, int displayId) {
+ public void hideIme(IBinder imeTargetWindowToken, int displayId,
+ @Nullable ImeTracker.Token statsToken) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme");
synchronized (mGlobalLock) {
WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
@@ -8086,10 +8051,15 @@
dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
}
if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
dc.getImeTarget(IME_TARGET_CONTROL));
- dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(
- WindowInsets.Type.ime(), true /* fromIme */);
+ dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
+ true /* fromIme */, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
}
if (dc != null) {
dc.getInsetsStateController().getImeSourceProvider().setImeShowing(false);
@@ -8330,6 +8300,17 @@
return true;
}
}
+
+ @Override
+ public SurfaceControl getA11yOverlayLayer(int displayId) {
+ synchronized (mGlobalLock) {
+ DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc != null) {
+ return dc.getA11yOverlayLayer();
+ }
+ }
+ return null;
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
@@ -8932,7 +8913,7 @@
outInsetsState.set(state, true /* copySources */);
if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
final float compatScale = token != null && token.hasSizeCompatBounds()
- ? token.getSizeCompatScale() * overrideScale
+ ? token.getCompatScale() * overrideScale
: overrideScale;
outInsetsState.scale(1f / compatScale);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 6f2930c..1b70d1d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -21,7 +21,7 @@
import static android.os.Process.myTid;
import static android.os.Process.setThreadPriority;
-import static com.android.server.LockGuard.INDEX_WINDOW;;
+import static com.android.server.LockGuard.INDEX_WINDOW;
import com.android.internal.annotations.GuardedBy;
import com.android.server.AnimationThread;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 7415376..8dc1f0b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -39,6 +39,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -48,6 +49,7 @@
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -144,6 +146,8 @@
@VisibleForTesting
final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
+ private final Rect mTmpBounds = new Rect();
+
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
@@ -397,9 +401,6 @@
*/
void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
- if (!isValidTransaction(wct)) {
- return;
- }
enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
Objects.requireNonNull(wct));
@@ -453,7 +454,7 @@
// calls startSyncSet.
() -> mTransitionController.moveToCollecting(nextTransition),
() -> {
- if (isValidTransaction(wct)) {
+ if (mTaskFragmentOrganizerController.isValidTransaction(wct)) {
applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
mTransitionController.requestStartTransition(nextTransition,
null /* startTask */, null /* remoteTransition */,
@@ -702,7 +703,7 @@
}
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
- int effects = 0;
+ int effects = applyChanges(tr, c, null /* errorCallbackToken */);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
@@ -717,6 +718,10 @@
effects = TRANSACT_EFFECTS_LIFECYCLE;
}
+ if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
+ tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
+ }
+
final int childWindowingMode = c.getActivityWindowingMode();
if (childWindowingMode > -1) {
tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
@@ -759,6 +764,7 @@
private int applyDisplayAreaChanges(DisplayArea displayArea,
WindowContainerTransaction.Change c) {
final int[] effects = new int[1];
+ effects[0] = applyChanges(displayArea, c, null /* errorCallbackToken */);
if ((c.getChangeMask()
& WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
@@ -779,6 +785,27 @@
return effects[0];
}
+ private int applyTaskFragmentChanges(@NonNull TaskFragment taskFragment,
+ @NonNull WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
+ if (taskFragment.isEmbeddedTaskFragmentInPip()) {
+ // No override from organizer for embedded TaskFragment in a PIP Task.
+ return 0;
+ }
+
+ // When the TaskFragment is resized, we may want to create a change transition for it, for
+ // which we want to defer the surface update until we determine whether or not to start
+ // change transition.
+ mTmpBounds.set(taskFragment.getBounds());
+ taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
+ final int effects = applyChanges(taskFragment, c, errorCallbackToken);
+ if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
+ taskFragment.initializeChangeTransition(mTmpBounds);
+ }
+ taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
+ mTmpBounds.set(0, 0, 0, 0);
+ return effects;
+ }
+
private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
@@ -967,6 +994,14 @@
tf1.setAdjacentTaskFragment(tf2);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ // Clear the focused app if the focused app is no longer visible after reset the
+ // adjacent TaskFragments.
+ if (tf2 == null && tf1.getDisplayContent().mFocusedApp != null
+ && tf1.hasChild(tf1.getDisplayContent().mFocusedApp)
+ && !tf1.shouldBeVisible(null /* starting */)) {
+ tf1.getDisplayContent().setFocusedApp(null);
+ }
+
final Bundle bundle = hop.getLaunchOptions();
final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams =
bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams(
@@ -1080,6 +1115,22 @@
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
break;
}
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT: {
+ final IBinder fragmentToken = hop.getContainer();
+ final IBinder companionToken = hop.getCompanionContainer();
+ final TaskFragment fragment = mLaunchTaskFragments.get(fragmentToken);
+ final TaskFragment companion = companionToken != null ? mLaunchTaskFragments.get(
+ companionToken) : null;
+ if (fragment == null || !fragment.isAttached()) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to set companion on invalid fragment tokens");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, fragment, type,
+ exception);
+ break;
+ }
+ fragment.setCompanionTaskFragment(companion);
+ break;
+ }
default: {
// The other operations may change task order so they are skipped while in lock
// task mode. The above operations are still allowed because they don't move
@@ -1444,20 +1495,15 @@
private int applyWindowContainerChange(WindowContainer wc,
WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
sanitizeWindowContainer(wc);
- if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) {
- // No override from organizer for embedded TaskFragment in a PIP Task.
- return 0;
+ if (wc.asDisplayArea() != null) {
+ return applyDisplayAreaChanges(wc.asDisplayArea(), c);
+ } else if (wc.asTask() != null) {
+ return applyTaskChanges(wc.asTask(), c);
+ } else if (wc.asTaskFragment() != null) {
+ return applyTaskFragmentChanges(wc.asTaskFragment(), c, errorCallbackToken);
+ } else {
+ return applyChanges(wc, c, errorCallbackToken);
}
-
- int effects = applyChanges(wc, c, errorCallbackToken);
-
- if (wc instanceof DisplayArea) {
- effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
- } else if (wc instanceof Task) {
- effects |= applyTaskChanges(wc.asTask(), c);
- }
-
- return effects;
}
@Override
@@ -1569,18 +1615,6 @@
return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
}
- private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
- if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
- .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
- // Transaction from an unregistered organizer should not be applied. This can happen
- // when the organizer process died before the transaction is applied.
- Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
- + " is no longer registered");
- return false;
- }
- return true;
- }
-
/**
* Makes sure that the transaction only contains operations that are allowed for the
* {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
@@ -1628,6 +1662,12 @@
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
enforceTaskFragmentOrganized(func, hop.getNewParent(), organizer);
break;
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+ enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
+ if (hop.getCompanionContainer() != null) {
+ enforceTaskFragmentOrganized(func, hop.getCompanionContainer(), organizer);
+ }
+ break;
case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
if (hop.getAdjacentRoot() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 369f1ed..19409b1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -24,12 +24,11 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
-import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
-import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.ITYPE_INVALID;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.ViewRootImpl.LOCAL_LAYOUT;
@@ -202,7 +201,6 @@
import android.graphics.RectF;
import android.graphics.Region;
import android.gui.TouchOcclusionMode;
-import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Build;
import android.os.Debug;
@@ -248,6 +246,7 @@
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import android.window.OnBackInvokedCallbackInfo;
@@ -259,6 +258,7 @@
import com.android.internal.util.ToBooleanFunction;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import dalvik.annotation.optimization.NeverCompile;
@@ -474,7 +474,7 @@
// Current transformation being applied.
float mGlobalScale = 1f;
float mInvGlobalScale = 1f;
- float mSizeCompatScale = 1f;
+ float mCompatScale = 1f;
final float mOverrideScale;
float mHScale = 1f, mVScale = 1f;
float mLastHScale = 1f, mLastVScale = 1f;
@@ -790,7 +790,7 @@
* preferredDisplayModeId or is part of the high refresh rate deny list.
* The variable is cached, so we do not send too many updates to SF.
*/
- float mAppPreferredFrameRate = 0f;
+ FrameRateVote mFrameRateVote = new FrameRateVote();
static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
@@ -1255,19 +1255,21 @@
void updateGlobalScale() {
if (hasCompatScale()) {
- mSizeCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
- ? mToken.getSizeCompatScale()
+ mCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
+ ? mToken.getCompatScale()
: 1f;
- mGlobalScale = mSizeCompatScale * mOverrideScale;
+ mGlobalScale = mCompatScale * mOverrideScale;
mInvGlobalScale = 1f / mGlobalScale;
return;
}
- mGlobalScale = mInvGlobalScale = mSizeCompatScale = 1f;
+ mGlobalScale = mInvGlobalScale = mCompatScale = 1f;
}
- float getSizeCompatScale() {
- return mSizeCompatScale;
+ float getCompatScaleForClient() {
+ // If this window in the size compat mode. The scaling is fully controlled at the server
+ // side. The client doesn't need to take it into account.
+ return mToken.hasSizeCompatBounds() ? 1f : mCompatScale;
}
/**
@@ -1536,10 +1538,11 @@
mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// If the orientation is changing, or we're starting or ending a drag resizing action,
- // then we need to hold off on unfreezing the display until this window has been
- // redrawn; to do that, we need to go through the process of getting informed by the
- // application when it has finished drawing.
- if (getOrientationChanging() || dragResizingChanged) {
+ // or we're resizing an embedded Activity, then we need to hold off on unfreezing the
+ // display until this window has been redrawn; to do that, we need to go through the
+ // process of getting informed by the application when it has finished drawing.
+ if (getOrientationChanging() || dragResizingChanged
+ || isEmbeddedActivityResizeChanged()) {
if (dragResizingChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize start waiting for draw, "
@@ -1674,11 +1677,14 @@
if (rotatedState != null) {
return insetsPolicy.adjustInsetsForWindow(this, rotatedState);
}
+ final InsetsSourceProvider provider = getControllableInsetProvider();
+ final @InternalInsetsType int insetTypeProvidedByWindow = provider != null
+ ? provider.getSource().getType() : ITYPE_INVALID;
final InsetsState rawInsetsState =
mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState();
final InsetsState insetsStateForWindow = insetsPolicy
- .enforceInsetsPolicyForTarget(
- getWindowingMode(), isAlwaysOnTop(), mAttrs, rawInsetsState);
+ .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow,
+ getWindowingMode(), isAlwaysOnTop(), mAttrs.type, rawInsetsState);
return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
includeTransient);
}
@@ -3861,7 +3867,8 @@
outFrames.attachedFrame.scale(mInvGlobalScale);
}
}
- outFrames.sizeCompatScale = mSizeCompatScale;
+
+ outFrames.compatScale = getCompatScaleForClient();
// Note: in the cases where the window is tied to an activity, we should not send a
// configuration update when the window has requested to be hidden. Doing so can lead to
@@ -4011,7 +4018,7 @@
mClient.insetsControlChanged(getCompatInsetsState(),
stateController.getControlsForDispatch(this));
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver inset state change to w=" + this, e);
+ Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e);
}
}
@@ -4021,20 +4028,30 @@
}
@Override
- public void showInsets(@InsetsType int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mClient.showInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
+ mClient.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mClient.hideInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
+ mClient.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver showInsets", e);
+ Slog.w(TAG, "Failed to deliver hideInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
@@ -4142,6 +4159,20 @@
return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
}
+ /**
+ * Whether this window belongs to a resizing embedded activity.
+ */
+ private boolean isEmbeddedActivityResizeChanged() {
+ if (mActivityRecord == null || !isVisibleRequested()) {
+ // No need to update if the window is in the background.
+ return false;
+ }
+
+ final TaskFragment embeddedTaskFragment = mActivityRecord.getOrganizedTaskFragment();
+ return embeddedTaskFragment != null
+ && mDisplayContent.mChangingContainers.contains(embeddedTaskFragment);
+ }
+
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}
@@ -5474,20 +5505,12 @@
mFrameRateSelectionPriority);
}
- // If refresh rate switching is disabled there is no point to set the frame rate on the
- // surface as the refresh rate will be limited by display manager to a single value
- // and SurfaceFlinger wouldn't be able to change it anyways.
- @DisplayManager.SwitchingType int refreshRateSwitchingType =
- mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
- if (refreshRateSwitchingType != SWITCHING_TYPE_NONE
- && refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
- final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
- if (mAppPreferredFrameRate != refreshRate) {
- mAppPreferredFrameRate = refreshRate;
- getPendingTransaction().setFrameRate(
- mSurfaceControl, mAppPreferredFrameRate,
- Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- }
+ boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
+ if (voteChanged) {
+ getPendingTransaction().setFrameRate(
+ mSurfaceControl, mFrameRateVote.mRefreshRate,
+ mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+
}
}
@@ -6011,7 +6034,7 @@
final long duration =
SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime;
Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
- mActivityRecord.mRelaunchStartTime = 0;
+ mActivityRecord.finishOrAbortReplacingWindow();
}
if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) {
mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
@@ -6103,10 +6126,10 @@
if (mRedrawForSyncReported) {
return false;
}
- // TODO(b/233286785): Remove mIsWallpaper once WallpaperService handles syncId of relayout.
- if (mInRelayout && !mIsWallpaper) {
- // The last sync seq id will return to the client, so there is no need to request the
- // client to redraw.
+ if (mInRelayout && (mPrepareSyncSeqId > 0 || (mViewVisibility == View.VISIBLE
+ && mWinAnimator.mDrawState == DRAW_PENDING))) {
+ // The client will report draw if it gets the sync seq id from relayout or it is
+ // drawing for being visible, then no need to request redraw.
return false;
}
return useBLASTSync();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7c481f5..f2527b6 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -258,7 +258,7 @@
* @return The scale for applications running in compatibility mode. Multiply the size in the
* application by this scale will be the size in the screen.
*/
- float getSizeCompatScale() {
+ float getCompatScale() {
return mDisplayContent.mCompatibleScreenScale;
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3c78819..f431250 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -135,6 +135,7 @@
"libschedulerservicehidl",
"libsensorservice",
"libsensorservicehidl",
+ "libsensorserviceaidl",
"libgui",
"libtimestats_atoms_proto",
"libusbhost",
@@ -170,7 +171,7 @@
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
- "android.hardware.power-V3-cpp",
+ "android.hardware.power-V4-cpp",
"android.hardware.power.stats@1.0",
"android.hardware.power.stats-V1-ndk",
"android.hardware.thermal@1.0",
@@ -184,6 +185,7 @@
"android.hidl.token@1.0-utils",
"android.frameworks.schedulerservice@1.0",
"android.frameworks.sensorservice@1.0",
+ "android.frameworks.sensorservice-V1-ndk",
"android.frameworks.stats@1.0",
"android.frameworks.stats-V2-ndk",
"android.system.suspend.control-V1-cpp",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index b171a07..be18f64 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -14,35 +14,31 @@
* limitations under the License.
*/
-#include <dlfcn.h>
-#include <pthread.h>
-
-#include <chrono>
-#include <thread>
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
+#include <android-base/properties.h>
#include <android/binder_manager.h>
#include <android/binder_stability.h>
#include <android/hidl/manager/1.2/IServiceManager.h>
#include <binder/IServiceManager.h>
+#include <bionic/malloc.h>
+#include <bionic/reserved_signals.h>
+#include <dlfcn.h>
#include <hidl/HidlTransportSupport.h>
#include <incremental_service.h>
-
+#include <jni.h>
#include <memtrackproxy/MemtrackProxy.h>
+#include <nativehelper/JNIHelp.h>
+#include <pthread.h>
#include <schedulerservice/SchedulingPolicyService.h>
+#include <sensorserviceaidl/SensorManagerAidl.h>
#include <sensorservicehidl/SensorManager.h>
#include <stats/StatsAidl.h>
#include <stats/StatsHal.h>
-
-#include <bionic/malloc.h>
-#include <bionic/reserved_signals.h>
-
-#include <android-base/properties.h>
+#include <utils/AndroidThreads.h>
#include <utils/Log.h>
#include <utils/misc.h>
-#include <utils/AndroidThreads.h>
+
+#include <chrono>
+#include <thread>
using namespace std::chrono_literals;
@@ -57,7 +53,9 @@
const std::string instance = std::string() + IStats::descriptor + "/default";
const binder_exception_t err =
AServiceManager_addService(statsService->asBinder().get(), instance.c_str());
- LOG_ALWAYS_FATAL_IF(err != EX_NONE, "Cannot register AIDL %s: %d", instance.c_str(), err);
+ if (err != EX_NONE) {
+ ALOGW("Cannot register AIDL %s: %d", instance.c_str(), err);
+ }
}
static void startStatsHidlService() {
@@ -69,6 +67,42 @@
ALOGW_IF(err != android::OK, "Cannot register HIDL %s: %d", IStats::descriptor, err);
}
+static void startSensorManagerAidlService(JNIEnv* env) {
+ using ::aidl::android::frameworks::sensorservice::ISensorManager;
+ using ::android::frameworks::sensorservice::implementation::SensorManagerAidl;
+
+ JavaVM* vm;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
+
+ std::shared_ptr<SensorManagerAidl> sensorService =
+ ndk::SharedRefBase::make<SensorManagerAidl>(vm);
+ const std::string instance = std::string() + ISensorManager::descriptor + "/default";
+ const binder_exception_t err =
+ AServiceManager_addService(sensorService->asBinder().get(), instance.c_str());
+ LOG_ALWAYS_FATAL_IF(err != EX_NONE, "Cannot register AIDL %s: %d", instance.c_str(), err);
+}
+
+static void startSensorManagerHidlService(JNIEnv* env) {
+ using ::android::frameworks::sensorservice::V1_0::ISensorManager;
+ using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager;
+ using ::android::hardware::configureRpcThreadpool;
+ using ::android::hidl::manager::V1_0::IServiceManager;
+
+ JavaVM* vm;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
+
+ android::sp<ISensorManager> sensorService = new SensorManager(vm);
+ if (IServiceManager::Transport::HWBINDER ==
+ android::hardware::defaultServiceManager1_2()->getTransport(ISensorManager::descriptor,
+ "default")) {
+ android::status_t err = sensorService->registerAsService();
+ LOG_ALWAYS_FATAL_IF(err != android::OK, "Cannot register %s: %d",
+ ISensorManager::descriptor, err);
+ } else {
+ ALOGW("%s is deprecated. Skipping registration.", ISensorManager::descriptor);
+ }
+}
+
} // namespace
namespace android {
@@ -78,6 +112,12 @@
startStatsAidlService();
}
+static void android_server_SystemServer_startISensorManagerService(JNIEnv* env,
+ jobject /* clazz */) {
+ startSensorManagerHidlService(env);
+ startSensorManagerAidlService(env);
+}
+
static void android_server_SystemServer_startMemtrackProxyService(JNIEnv* env,
jobject /* clazz */) {
using aidl::android::hardware::memtrack::MemtrackProxy;
@@ -93,35 +133,19 @@
LOG_ALWAYS_FATAL_IF(err != EX_NONE, "Cannot register %s: %d", memtrackProxyService, err);
}
-static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject /* clazz */) {
+static void android_server_SystemServer_startHidlServices(JNIEnv* /* env */, jobject /* clazz */) {
using ::android::frameworks::schedulerservice::V1_0::ISchedulingPolicyService;
using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService;
- using ::android::frameworks::sensorservice::V1_0::ISensorManager;
- using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager;
using ::android::hardware::configureRpcThreadpool;
using ::android::hidl::manager::V1_0::IServiceManager;
- status_t err;
-
configureRpcThreadpool(5, false /* callerWillJoin */);
- JavaVM *vm;
- LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
-
- sp<ISensorManager> sensorService = new SensorManager(vm);
- if (IServiceManager::Transport::HWBINDER ==
- hardware::defaultServiceManager1_2()->getTransport(ISensorManager::descriptor, "default")) {
- err = sensorService->registerAsService();
- LOG_ALWAYS_FATAL_IF(err != OK, "Cannot register %s: %d", ISensorManager::descriptor, err);
- } else {
- ALOGW("%s is deprecated. Skipping registration.", ISensorManager::descriptor);
- }
-
sp<ISchedulingPolicyService> schedulingService = new SchedulingPolicyService();
if (IServiceManager::Transport::HWBINDER ==
hardware::defaultServiceManager1_2()->getTransport(ISchedulingPolicyService::descriptor,
"default")) {
- err = schedulingService->registerAsService("default");
+ status_t err = schedulingService->registerAsService("default");
LOG_ALWAYS_FATAL_IF(err != OK, "Cannot register %s: %d",
ISchedulingPolicyService::descriptor, err);
} else {
@@ -156,6 +180,8 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{"startIStatsService", "()V", (void*)android_server_SystemServer_startIStatsService},
+ {"startISensorManagerService", "()V",
+ (void*)android_server_SystemServer_startISensorManagerService},
{"startMemtrackProxyService", "()V",
(void*)android_server_SystemServer_startMemtrackProxyService},
{"startHidlServices", "()V", (void*)android_server_SystemServer_startHidlServices},
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 16eaa77..3678ced 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -98,7 +98,7 @@
public:
binder::Status notifyWakeup(bool success,
const std::vector<std::string>& wakeupReasons) override {
- ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
+ ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
bool reasonsCaptured = false;
{
std::unique_lock<std::mutex> reasonsLock(mReasonsMutex, std::defer_lock);
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index 000cb83..d975760 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -34,6 +34,7 @@
#include "jni.h"
using android::hardware::power::IPowerHintSession;
+using android::hardware::power::SessionHint;
using android::hardware::power::WorkDuration;
using android::base::StringPrintf;
@@ -81,6 +82,11 @@
appSession->reportActualWorkDuration(actualDurations);
}
+static void sendHint(int64_t session_ptr, SessionHint hint) {
+ sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ appSession->sendHint(hint);
+}
+
static int64_t getHintSessionPreferredRate() {
int64_t rate = -1;
auto result = gPowerHalController.getHintSessionPreferredRate();
@@ -139,6 +145,10 @@
reportActualWorkDuration(session_ptr, actualList);
}
+static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) {
+ sendHint(session_ptr, static_cast<SessionHint>(hint));
+}
+
static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
return static_cast<jlong>(getHintSessionPreferredRate());
}
@@ -153,6 +163,7 @@
{"nativeCloseHintSession", "(J)V", (void*)nativeCloseHintSession},
{"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
{"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
+ {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
{"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
};
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0d87237..5d0551b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -260,10 +260,9 @@
// --- NativeInputManager ---
-class NativeInputManager : public virtual RefBase,
- public virtual InputReaderPolicyInterface,
- public virtual InputDispatcherPolicyInterface,
- public virtual PointerControllerPolicyInterface {
+class NativeInputManager : public virtual InputReaderPolicyInterface,
+ public virtual InputDispatcherPolicyInterface,
+ public virtual PointerControllerPolicyInterface {
protected:
virtual ~NativeInputManager();
@@ -450,6 +449,10 @@
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
mLocked.pointerCaptureRequest.seq);
+ auto pointerController = mLocked.pointerController.lock();
+ if (pointerController != nullptr) {
+ pointerController->dump(dump);
+ }
}
dump += "\n";
@@ -1188,8 +1191,9 @@
static_cast<const KeyEvent*>(inputEvent));
break;
case AINPUT_EVENT_TYPE_MOTION:
- inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
- static_cast<const MotionEvent*>(inputEvent));
+ inputEventObj =
+ android_view_MotionEvent_obtainAsCopy(env,
+ static_cast<const MotionEvent&>(*inputEvent));
break;
default:
return true; // dispatch the event normally
@@ -1516,8 +1520,14 @@
return 0;
}
- NativeInputManager* im = new NativeInputManager(serviceObj, messageQueue->getLooper());
- im->incStrong(0);
+ static std::once_flag nativeInitialize;
+ NativeInputManager* im = nullptr;
+ std::call_once(nativeInitialize, [&]() {
+ // Create the NativeInputManager, which should not be destroyed or deallocated for the
+ // lifetime of the process.
+ im = new NativeInputManager(serviceObj, messageQueue->getLooper());
+ });
+ LOG_ALWAYS_FATAL_IF(im == nullptr, "NativeInputManager was already initialized.");
return reinterpret_cast<jlong>(im);
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 9fa23c2..e1de05c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -482,6 +482,17 @@
agnssRilIface->setSetId(type, setid_string);
}
+static void android_location_gnss_hal_GnssNative_inject_ni_supl_message_data(JNIEnv* env, jclass,
+ jbyteArray data,
+ jint length,
+ jint slotIndex) {
+ if (agnssRilIface == nullptr) {
+ ALOGE("%s: IAGnssRil interface not available.", __func__);
+ return;
+ }
+ agnssRilIface->injectNiSuplMessageData(data, length, slotIndex);
+}
+
static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass,
jbyteArray nmeaArray, jint buffer_size) {
return gnssHal->readNmea(nmeaArray, buffer_size);
@@ -974,6 +985,8 @@
android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid)},
{"native_set_agps_server", "(ILjava/lang/String;I)V",
reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)},
+ {"native_inject_ni_supl_message_data", "([BII)V",
+ reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)},
{"native_send_ni_response", "(II)V",
reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)},
{"native_get_internal_state", "()Ljava/lang/String;",
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index 34e4976..c7a1af7 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -84,8 +84,19 @@
networkAttributes.capabilities = static_cast<int32_t>(capabilities),
networkAttributes.apn = jniApn.c_str();
- auto result = mIAGnssRil->updateNetworkState(networkAttributes);
- return checkAidlStatus(result, "IAGnssRilAidl updateNetworkState() failed.");
+ auto status = mIAGnssRil->updateNetworkState(networkAttributes);
+ return checkAidlStatus(status, "IAGnssRilAidl updateNetworkState() failed.");
+}
+
+jboolean AGnssRil::injectNiSuplMessageData(const jbyteArray& msgData, jint length, jint slotIndex) {
+ JNIEnv* env = getJniEnv();
+ jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(msgData, 0));
+ auto status = mIAGnssRil->injectNiSuplMessageData(std::vector<uint8_t>((const uint8_t*)bytes,
+ (const uint8_t*)bytes +
+ length),
+ static_cast<int>(slotIndex));
+ env->ReleasePrimitiveArrayCritical(msgData, bytes, JNI_ABORT);
+ return checkAidlStatus(status, "IAGnssRil injectNiSuplMessageData() failed.");
}
// Implementation of AGnssRil_V1_0
@@ -151,6 +162,11 @@
return checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkState() failed.");
}
+jboolean AGnssRil_V1_0::injectNiSuplMessageData(const jbyteArray&, jint, jint) {
+ ALOGI("IAGnssRil_V1_0 interface does not support injectNiSuplMessageData.");
+ return JNI_FALSE;
+}
+
// Implementation of AGnssRil_V2_0
AGnssRil_V2_0::AGnssRil_V2_0(const sp<IAGnssRil_V2_0>& iAGnssRil)
diff --git a/services/core/jni/gnss/AGnssRil.h b/services/core/jni/gnss/AGnssRil.h
index ce14a77d..b7e0282 100644
--- a/services/core/jni/gnss/AGnssRil.h
+++ b/services/core/jni/gnss/AGnssRil.h
@@ -43,6 +43,8 @@
virtual jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming,
jboolean available, const jstring& apn, jlong networkHandle,
jshort capabilities) = 0;
+ virtual jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+ jint slotIndex) = 0;
};
class AGnssRil : public AGnssRilInterface {
@@ -55,6 +57,8 @@
jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
const jstring& apn, jlong networkHandle,
jshort capabilities) override;
+ jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+ jint slotIndex) override;
private:
const sp<android::hardware::gnss::IAGnssRil> mIAGnssRil;
@@ -70,6 +74,7 @@
jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
const jstring& apn, jlong networkHandle,
jshort capabilities) override;
+ jboolean injectNiSuplMessageData(const jbyteArray&, jint, jint) override;
private:
const sp<android::hardware::gnss::V1_0::IAGnssRil> mAGnssRil_V1_0;
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
new file mode 100644
index 0000000..06d8e62
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -0,0 +1,104 @@
+/*
+ * 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.server.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.credentials.CreateCredentialResponse;
+import android.credentials.CredentialManager;
+import android.credentials.ICreateCredentialCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.os.RemoteException;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Central session for a single {@link CredentialManager#executeCreateCredential} request.
+ * This class listens to the responses from providers, and the UX app, and updates the
+ * provider(s) state maintained in {@link ProviderCreateSession}.
+ */
+public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
+ ICreateCredentialCallback>
+ implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
+ private static final String TAG = "CreateRequestSession";
+
+ CreateRequestSession(@NonNull Context context, int userId,
+ CreateCredentialRequest request,
+ ICreateCredentialCallback callback,
+ String callingPackage) {
+ super(context, userId, request, callback, RequestInfo.TYPE_CREATE, callingPackage);
+ }
+
+ /**
+ * Creates a new provider session, and adds it to list of providers that are contributing to
+ * this request session.
+ *
+ * @return the provider session that was started
+ */
+ @Override
+ @Nullable
+ public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService) {
+ ProviderCreateSession providerCreateSession = ProviderCreateSession
+ .createNewSession(mContext, mUserId, providerInfo,
+ this, remoteCredentialService);
+ if (providerCreateSession != null) {
+ Log.i(TAG, "In startProviderSession - provider session created and being added");
+ mProviders.put(providerCreateSession.getComponentName().flattenToString(),
+ providerCreateSession);
+ }
+ return providerCreateSession;
+ }
+
+ @Override
+ protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+ mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newCreateRequestInfo(
+ mRequestId, mClientRequest, mIsFirstUiTurn, mClientCallingPackage),
+ providerDataList));
+ }
+
+ private void respondToClientAndFinish(CreateCredentialResponse response) {
+ Log.i(TAG, "respondToClientAndFinish");
+ try {
+ mClientCallback.onResponse(response);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ finishSession();
+ }
+
+ @Override
+ public void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ super.onProviderStatusChanged(status, componentName);
+ }
+
+ @Override
+ public void onFinalResponseReceived(ComponentName componentName,
+ CreateCredentialResponse response) {
+ Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ if (response != null) {
+ respondToClientAndFinish(response);
+ }
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 321f022..374da1c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialSessionCallback;
import android.credentials.ICreateCredentialCallback;
@@ -33,6 +34,7 @@
import android.os.ICancellationSignal;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.credentials.GetCredentialsRequest;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -43,6 +45,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Entry point service for credential management.
@@ -91,17 +94,15 @@
return new ArrayList<>();
}
List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
- for (int i = 0; i < serviceNames.length; i++) {
- Log.i(TAG, "in newServiceListLocked, service: " + serviceNames[i]);
- if (TextUtils.isEmpty(serviceNames[i])) {
+ for (String serviceName : serviceNames) {
+ Log.i(TAG, "in newServiceListLocked, service: " + serviceName);
+ if (TextUtils.isEmpty(serviceName)) {
continue;
}
try {
serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
- serviceNames[i]));
- } catch (PackageManager.NameNotFoundException e) {
- Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
- } catch (SecurityException e) {
+ serviceName));
+ } catch (PackageManager.NameNotFoundException | SecurityException e) {
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
}
}
@@ -115,15 +116,31 @@
synchronized (mLock) {
final List<CredentialManagerServiceImpl> services =
getServiceListForUserLocked(userId);
- services.forEach(s -> {
+ for (CredentialManagerServiceImpl s : services) {
c.accept(s);
- });
+ }
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ private List<ProviderSession> initiateProviderSessions(RequestSession session,
+ List<String> requestOptions) {
+ List<ProviderSession> providerSessions = new ArrayList<>();
+ // Invoke all services of a user to initiate a provider session
+ runForUser((service) -> {
+ if (service.isServiceCapable(requestOptions)) {
+ ProviderSession providerSession = service
+ .initiateProviderSessionForRequest(session);
+ if (providerSession != null) {
+ providerSessions.add(providerSession);
+ }
+ }
+ });
+ return providerSessions;
+ }
+
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@Override
public ICancellationSignal executeGetCredential(
@@ -137,11 +154,22 @@
// New request session, scoped for this request only.
final GetRequestSession session = new GetRequestSession(getContext(),
UserHandle.getCallingUserId(),
- callback);
+ callback,
+ request,
+ callingPackage);
- // Invoke all services of a user
- runForUser((service) -> {
- service.getCredential(request, session, callingPackage);
+ // Initiate all provider sessions
+ List<ProviderSession> providerSessions =
+ initiateProviderSessions(session, request.getGetCredentialOptions()
+ .stream().map(GetCredentialOption::getType)
+ .collect(Collectors.toList()));
+ // TODO : Return error when no providers available
+
+ // Iterate over all provider sessions and invoke the request
+ providerSessions.forEach(providerGetSession -> {
+ providerGetSession.getRemoteCredentialService().onGetCredentials(
+ (GetCredentialsRequest) providerGetSession.getProviderRequest(),
+ /*callback=*/providerGetSession);
});
return cancelTransport;
}
@@ -151,9 +179,29 @@
CreateCredentialRequest request,
ICreateCredentialCallback callback,
String callingPackage) {
- // TODO: implement.
- Log.i(TAG, "executeCreateCredential");
+ Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
+ // TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ // New request session, scoped for this request only.
+ final CreateRequestSession session = new CreateRequestSession(getContext(),
+ UserHandle.getCallingUserId(),
+ request,
+ callback,
+ callingPackage);
+
+ // Initiate all provider sessions
+ List<ProviderSession> providerSessions =
+ initiateProviderSessions(session, List.of(request.getType()));
+ // TODO : Return error when no providers available
+
+ // Iterate over all provider sessions and invoke the request
+ providerSessions.forEach(providerCreateSession -> {
+ providerCreateSession.getRemoteCredentialService().onCreateCredential(
+ (android.service.credentials.CreateCredentialRequest)
+ providerCreateSession.getProviderRequest(),
+ /*callback=*/providerCreateSession);
+ });
return cancelTransport;
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index cc03f9b..0c32304 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,13 +21,13 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.credentials.GetCredentialRequest;
import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.GetCredentialsRequest;
import android.util.Slog;
import com.android.server.infra.AbstractPerUserSystemService;
+import java.util.List;
+
/**
* Per-user, per remote service implementation of {@link CredentialManagerService}
@@ -61,50 +61,38 @@
return mInfo.getServiceInfo();
}
- public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
- String callingPackage) {
- Slog.i(TAG, "in getCredential in CredManServiceImpl");
+ /**
+ * Starts a provider session and associates it with the given request session. */
+ @Nullable
+ public ProviderSession initiateProviderSessionForRequest(
+ RequestSession requestSession) {
+ Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl");
if (mInfo == null) {
- Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
- return;
+ Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+ + "but mInfo is null. This shouldn't happen");
+ return null;
}
-
- // TODO : Determine if remoteService instance can be reused across requests
final RemoteCredentialService remoteService = new RemoteCredentialService(
getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
- ProviderGetSession providerSession = new ProviderGetSession(mInfo,
- requestSession, mUserId, remoteService);
- // Set the provider info to the session when the request is initiated. This happens here
- // because there is one serviceImpl per remote provider, and so we can only retrieve
- // the provider information in the scope of this instance, whereas the session is for the
- // entire request.
- requestSession.addProviderSession(providerSession);
- GetCredentialsRequest filteredRequest = getRequestWithValidType(request, callingPackage);
- if (filteredRequest != null) {
- remoteService.onGetCredentials(getRequestWithValidType(request, callingPackage),
- providerSession);
- }
+ ProviderSession providerSession =
+ requestSession.initiateProviderSession(mInfo, remoteService);
+ return providerSession;
}
- @Nullable
- private GetCredentialsRequest getRequestWithValidType(GetCredentialRequest request,
- String callingPackage) {
- GetCredentialsRequest.Builder builder =
- new GetCredentialsRequest.Builder(callingPackage);
- request.getGetCredentialOptions().forEach( option -> {
- if (mInfo.hasCapability(option.getType())) {
- Slog.i(TAG, "Provider can handle: " + option.getType());
- builder.addGetCredentialOption(option);
- } else {
- Slog.i(TAG, "Skipping request as provider cannot handle it");
- }
- });
-
- try {
- return builder.build();
- } catch (IllegalArgumentException | NullPointerException e) {
- Slog.i(TAG, "issue with request build: " + e.getMessage());
+ /** Return true if at least one capability found. */
+ boolean isServiceCapable(List<String> requestedOptions) {
+ if (mInfo == null) {
+ Slog.i(TAG, "in isServiceCapable, mInfo is null");
+ return false;
}
- return null;
+ for (String capability : requestedOptions) {
+ if (mInfo.hasCapability(capability)) {
+ Slog.i(TAG, "Provider can handle: " + capability);
+ return true;
+ } else {
+ Slog.i(TAG, "Provider cannot handle: " + capability);
+ }
+ }
+ return false;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index dcf094f..e889594 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -37,6 +37,7 @@
@NonNull
private final CredentialManagerUiCallback mCallbacks;
@NonNull private final Context mContext;
+ // TODO : Use for starting the activity for this user
private final int mUserId;
@NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
new Handler(Looper.getMainLooper())) {
@@ -56,7 +57,7 @@
Slog.i(TAG, "No selection found in UI result");
}
} else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) {
- mCallbacks.onUiCancelation();
+ mCallbacks.onUiCancellation();
}
}
@@ -67,7 +68,7 @@
/** Called when the user makes a selection. */
void onUiSelection(UserSelectionDialogResult selection);
/** Called when the user cancels the UI. */
- void onUiCancelation();
+ void onUiCancellation();
}
public CredentialManagerUi(Context context, int userId,
CredentialManagerUiCallback callbacks) {
@@ -83,9 +84,8 @@
*/
public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
Log.i(TAG, "In show");
- Intent intent = IntentFactory.newIntent(
- requestInfo, providerDataList,
- new ArrayList<>(), mResultReceiver);
+ Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, new ArrayList<>(),
+ mResultReceiver);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 80f0fec..8a698ca 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,149 +16,86 @@
package com.android.server.credentials;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
-import android.credentials.Credential;
+import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
-import android.credentials.ui.UserSelectionDialogResult;
import android.os.RemoteException;
-import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
-import android.util.Slog;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Central session for a single getCredentials request. This class listens to the
* responses from providers, and the UX app, and updates the provider(S) state.
*/
-public final class GetRequestSession extends RequestSession {
+public final class GetRequestSession extends RequestSession<GetCredentialRequest,
+ IGetCredentialCallback>
+ implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetRequestSession";
- private final IGetCredentialCallback mClientCallback;
- private final Map<String, ProviderGetSession> mProviders;
-
public GetRequestSession(Context context, int userId,
- IGetCredentialCallback callback) {
- super(context, userId, RequestInfo.TYPE_GET);
- mClientCallback = callback;
- mProviders = new HashMap<>();
+ IGetCredentialCallback callback, GetCredentialRequest request,
+ String callingPackage) {
+ super(context, userId, request, callback, RequestInfo.TYPE_GET, callingPackage);
}
/**
- * Adds a new provider to the list of providers that are contributing to this session.
+ * Creates a new provider session, and adds it list of providers that are contributing to
+ * this session.
+ * @return the provider session created within this request session, for the given provider
+ * info.
*/
- public void addProviderSession(ProviderGetSession providerSession) {
- mProviders.put(providerSession.getComponentName().flattenToString(),
- providerSession);
+ @Override
+ @Nullable
+ public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService) {
+ ProviderGetSession providerGetSession = ProviderGetSession
+ .createNewSession(mContext, mUserId, providerInfo,
+ this, remoteCredentialService);
+ if (providerGetSession != null) {
+ Log.i(TAG, "In startProviderSession - provider session created and being added");
+ mProviders.put(providerGetSession.getComponentName().flattenToString(),
+ providerGetSession);
+ }
+ return providerGetSession;
}
@Override
- public void onProviderStatusChanged(ProviderSession.Status status,
- ComponentName componentName) {
- Log.i(TAG, "in onStatusChanged");
- if (ProviderSession.isTerminatingStatus(status)) {
- Log.i(TAG, "in onStatusChanged terminating status");
-
- ProviderGetSession session = mProviders.remove(componentName.flattenToString());
- if (session != null) {
- Slog.i(TAG, "Provider session removed.");
- } else {
- Slog.i(TAG, "Provider session null, did not exist.");
- }
- } else if (ProviderSession.isCompletionStatus(status)) {
- Log.i(TAG, "in onStatusChanged isCompletionStatus status");
- onProviderResponseComplete();
- }
- }
-
- @Override
- public void onUiSelection(UserSelectionDialogResult selection) {
- String providerId = selection.getProviderId();
- ProviderGetSession providerSession = mProviders.get(providerId);
- if (providerSession != null) {
- CredentialEntry credentialEntry = providerSession.getCredentialEntry(
- selection.getEntrySubkey());
- if (credentialEntry != null && credentialEntry.getCredential() != null) {
- respondToClientAndFinish(credentialEntry.getCredential());
- }
- // TODO : Handle action chips and authentication selection
- return;
- }
- // TODO : finish session and respond to client if provider not found
- }
-
- @Override
- public void onUiCancelation() {
- // User canceled the activity
- // TODO : Send error code to client
- finishSession();
- }
-
- private void onProviderResponseComplete() {
- Log.i(TAG, "in onProviderResponseComplete");
- if (isResponseCompleteAcrossProviders()) {
- Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
- getProviderDataAndInitiateUi();
- }
- }
-
- private void getProviderDataAndInitiateUi() {
- ArrayList<ProviderData> providerDataList = new ArrayList<>();
- for (ProviderGetSession session : mProviders.values()) {
- Log.i(TAG, "preparing data for : " + session.getComponentName());
- providerDataList.add(session.prepareUiData());
- }
- if (!providerDataList.isEmpty()) {
- Log.i(TAG, "provider list not empty about to initiate ui");
- initiateUi(providerDataList);
- }
- }
-
- private void initiateUi(ArrayList<ProviderData> providerDataList) {
+ protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
mRequestId, null, mIsFirstUiTurn, ""),
providerDataList));
}
- /**
- * Iterates over all provider sessions and returns true if all have responded.
- */
- private boolean isResponseCompleteAcrossProviders() {
- AtomicBoolean isRequestComplete = new AtomicBoolean(true);
- mProviders.forEach( (packageName, session) -> {
- if (session.getStatus() != ProviderSession.Status.COMPLETE) {
- isRequestComplete.set(false);
- }
- });
- return isRequestComplete.get();
+ @Override // from provider session
+ public void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ super.onProviderStatusChanged(status, componentName);
}
- private void respondToClientAndFinish(Credential credential) {
+
+ @Override
+ public void onFinalResponseReceived(ComponentName componentName,
+ GetCredentialResponse response) {
+ Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ if (response != null) {
+ respondToClientAndFinish(response);
+ }
+ }
+
+ private void respondToClientAndFinish(GetCredentialResponse response) {
+ Log.i(TAG, "respondToClientAndFinish");
try {
- mClientCallback.onResponse(new GetCredentialResponse(credential));
+ mClientCallback.onResponse(response);
} catch (RemoteException e) {
e.printStackTrace();
}
finishSession();
}
-
- private void finishSession() {
- clearProviderSessions();
- }
-
- private void clearProviderSessions() {
- for (ProviderGetSession session : mProviders.values()) {
- // TODO : Evaluate if we should unbind remote services here or wait for them
- // to automatically unbind when idle. Re-binding frequently also has a cost.
- //session.destroy();
- }
- mProviders.clear();
- }
}
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
new file mode 100644
index 0000000..4cdc457
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -0,0 +1,69 @@
+/*
+ * 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.server.credentials;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.credentials.CreateCredentialResponse;
+import android.credentials.Credential;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.CredentialsDisplayContent;
+
+/**
+ * Helper class for setting up pending intent, and extracting objects from it.
+ *
+ * @hide
+ */
+public class PendingIntentResultHandler {
+ /** Returns true if the result is successful and may contain result extras. */
+ public static boolean isSuccessfulResponse(
+ ProviderPendingIntentResponse pendingIntentResponse) {
+ //TODO: Differentiate based on extra_error in the resultData
+ return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
+ }
+
+ /** Extracts the {@link CredentialsDisplayContent} object added to the result data. */
+ public static CredentialsDisplayContent extractCredentialsDisplayContent(Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT,
+ CredentialsDisplayContent.class);
+ }
+
+ /** Extracts the {@link CreateCredentialResponse} object added to the result data. */
+ public static CreateCredentialResponse extractCreateCredentialResponse(Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESPONSE,
+ CreateCredentialResponse.class);
+ }
+
+ /** Extracts the {@link Credential} object added to the result data. */
+ public static Credential extractCredential(Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL,
+ Credential.class);
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
new file mode 100644
index 0000000..bf37bd2
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -0,0 +1,242 @@
+/*
+ * 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.server.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.credentials.ui.CreateCredentialProviderData;
+import android.credentials.ui.Entry;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.Bundle;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.SaveEntry;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ * Will likely split this into remote response state and UI state.
+ */
+public final class ProviderCreateSession extends ProviderSession<
+ CreateCredentialRequest, CreateCredentialResponse> {
+ private static final String TAG = "ProviderCreateSession";
+
+ // Key to be used as an entry key for a save entry
+ private static final String SAVE_ENTRY_KEY = "save_entry_key";
+
+ @NonNull
+ private final Map<String, SaveEntry> mUiSaveEntries = new HashMap<>();
+ /** The complete request to be used in the second round. */
+ private final CreateCredentialRequest mCompleteRequest;
+
+ /** Creates a new provider session to be used by the request session. */
+ @Nullable public static ProviderCreateSession createNewSession(
+ Context context,
+ @UserIdInt int userId,
+ CredentialProviderInfo providerInfo,
+ CreateRequestSession createRequestSession,
+ RemoteCredentialService remoteCredentialService) {
+ CreateCredentialRequest providerRequest =
+ createProviderRequest(providerInfo.getCapabilities(),
+ createRequestSession.mClientRequest,
+ createRequestSession.mClientCallingPackage);
+ if (providerRequest != null) {
+ return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
+ remoteCredentialService, providerRequest);
+ }
+ Log.i(TAG, "Unable to create provider session");
+ return null;
+ }
+
+ @Nullable
+ private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
+ android.credentials.CreateCredentialRequest clientRequest,
+ String clientCallingPackage) {
+ String capability = clientRequest.getType();
+ if (providerCapabilities.contains(capability)) {
+ return new CreateCredentialRequest(clientCallingPackage, capability,
+ clientRequest.getData());
+ }
+ Log.i(TAG, "Unable to create provider request - capabilities do not match");
+ return null;
+ }
+
+ private static CreateCredentialRequest getFirstRoundRequest(CreateCredentialRequest request) {
+ // TODO: Replace with first round bundle from request when ready
+ return new CreateCredentialRequest(
+ request.getCallingPackage(),
+ request.getType(),
+ new Bundle());
+ }
+
+ private ProviderCreateSession(
+ @NonNull Context context,
+ @NonNull CredentialProviderInfo info,
+ @NonNull ProviderInternalCallback callbacks,
+ @UserIdInt int userId,
+ @NonNull RemoteCredentialService remoteCredentialService,
+ @NonNull CreateCredentialRequest request) {
+ super(context, info, getFirstRoundRequest(request), callbacks, userId,
+ remoteCredentialService);
+ // TODO : Replace with proper splitting of request
+ mCompleteRequest = request;
+ setStatus(Status.PENDING);
+ }
+
+ /** Returns the save entry maintained in state by this provider session. */
+ public SaveEntry getUiSaveEntry(String entryId) {
+ return mUiSaveEntries.get(entryId);
+ }
+
+ @Override
+ public void onProviderResponseSuccess(
+ @Nullable CreateCredentialResponse response) {
+ Log.i(TAG, "in onProviderResponseSuccess");
+ onUpdateResponse(response);
+ }
+
+ /** Called when the provider response resulted in a failure. */
+ @Override
+ public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
+ updateStatusAndInvokeCallback(toStatus(errorCode));
+ }
+
+ /** Called when provider service dies. */
+ @Override
+ public void onProviderServiceDied(RemoteCredentialService service) {
+ if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
+ updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+ } else {
+ Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ + "this should not happen");
+ }
+ }
+
+ private void onUpdateResponse(CreateCredentialResponse response) {
+ Log.i(TAG, "updateResponse with save entries");
+ mProviderResponse = response;
+ updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
+ }
+
+ @Override
+ @Nullable protected CreateCredentialProviderData prepareUiData()
+ throws IllegalArgumentException {
+ Log.i(TAG, "In prepareUiData");
+ if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+ Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+ return null;
+ }
+ final CreateCredentialResponse response = getProviderResponse();
+ if (response == null) {
+ Log.i(TAG, "In prepareUiData response null");
+ throw new IllegalStateException("Response must be in completion mode");
+ }
+ if (response.getSaveEntries() != null) {
+ Log.i(TAG, "In prepareUiData save entries not null");
+ return prepareUiProviderData(
+ prepareUiSaveEntries(response.getSaveEntries()),
+ null,
+ /*isDefaultProvider=*/false);
+ }
+ return null;
+ }
+
+ @Override
+ public void onUiEntrySelected(String entryType, String entryKey,
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ switch (entryType) {
+ case SAVE_ENTRY_KEY:
+ if (mUiSaveEntries.containsKey(entryKey)) {
+ onSaveEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Unexpected save entry key");
+ }
+ break;
+ case REMOTE_ENTRY_KEY:
+ if (mUiRemoteEntry.first.equals(entryKey)) {
+ onRemoteEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Unexpected remote entry key");
+ }
+ break;
+ default:
+ Log.i(TAG, "Unsupported entry type selected");
+ }
+ }
+
+ private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+ Log.i(TAG, "in populateUiSaveEntries");
+ List<Entry> uiSaveEntries = new ArrayList<>();
+
+ // Populate the save entries
+ for (SaveEntry saveEntry : saveEntries) {
+ String entryId = generateEntryId();
+ mUiSaveEntries.put(entryId, saveEntry);
+ Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
+ uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice(),
+ saveEntry.getPendingIntent(), setUpFillInIntent(saveEntry.getPendingIntent())));
+ }
+ return uiSaveEntries;
+ }
+
+ private Intent setUpFillInIntent(PendingIntent pendingIntent) {
+ Intent intent = pendingIntent.getIntent();
+ intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
+ mCompleteRequest.getData());
+ return intent;
+ }
+
+ private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
+ Entry remoteEntry, boolean isDefaultProvider) {
+ return new CreateCredentialProviderData.Builder(
+ mComponentName.flattenToString())
+ .setSaveEntries(saveEntries)
+ .setIsDefaultProvider(isDefaultProvider)
+ .build();
+ }
+
+ private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
+ if (pendingIntentResponse == null) {
+ return;
+ //TODO: Handle failure if pending intent is null
+ }
+ if (PendingIntentResultHandler.isSuccessfulResponse(pendingIntentResponse)) {
+ android.credentials.CreateCredentialResponse credentialResponse =
+ PendingIntentResultHandler.extractCreateCredentialResponse(
+ pendingIntentResponse.getResultData());
+ if (credentialResponse != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
+ return;
+ }
+ }
+ //TODO: Handle failure case is pending intent response does not have a credential
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index ff2107a..d63cdeb 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -18,15 +18,22 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.slice.Slice;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.Credential;
+import android.credentials.GetCredentialOption;
+import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import java.util.ArrayList;
@@ -38,75 +45,102 @@
/**
* Central provider session that listens for provider callbacks, and maintains provider state.
* Will likely split this into remote response state and UI state.
+ *
+ * @hide
*/
-public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
- implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
+public final class ProviderGetSession extends ProviderSession<GetCredentialsRequest,
+ GetCredentialsResponse>
+ implements
+ RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
private static final String TAG = "ProviderGetSession";
// Key to be used as an entry key for a credential entry
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
- private GetCredentialsResponse mResponse;
+ // Key to be used as the entry key for an action entry
+ private static final String ACTION_ENTRY_KEY = "action_key";
+ // Key to be used as the entry key for the authentication entry
+ private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
@NonNull
- private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
-
+ private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@NonNull
- private final Map<String, Action> mUiActions = new HashMap<>();
-
- public ProviderGetSession(CredentialProviderInfo info,
- ProviderInternalCallback callbacks,
- int userId, RemoteCredentialService remoteCredentialService) {
- super(info, callbacks, userId, remoteCredentialService);
- setStatus(Status.PENDING);
- }
-
- /** Updates the response being maintained in state by this provider session. */
- @Override
- public void updateResponse(GetCredentialsResponse response) {
- if (response.getAuthenticationAction() != null) {
- // TODO : Implement authentication logic
- } else if (response.getCredentialsDisplayContent() != null) {
- Log.i(TAG , "updateResponse with credentialEntries");
- mResponse = response;
- updateStatusAndInvokeCallback(Status.COMPLETE);
- }
- }
-
- /** Returns the response being maintained in this provider session. */
- @Override
+ private final Map<String, Action> mUiActionsEntries = new HashMap<>();
@Nullable
- public GetCredentialsResponse getResponse() {
- return mResponse;
+ private Pair<String, Action> mUiAuthenticationAction = null;
+
+ /** Creates a new provider session to be used by the request session. */
+ @Nullable public static ProviderGetSession createNewSession(
+ Context context,
+ @UserIdInt int userId,
+ CredentialProviderInfo providerInfo,
+ GetRequestSession getRequestSession,
+ RemoteCredentialService remoteCredentialService) {
+ GetCredentialsRequest providerRequest =
+ createProviderRequest(providerInfo.getCapabilities(),
+ getRequestSession.mClientRequest,
+ getRequestSession.mClientCallingPackage);
+ if (providerRequest != null) {
+ return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
+ remoteCredentialService, providerRequest);
+ }
+ Log.i(TAG, "Unable to create provider session");
+ return null;
+ }
+
+ @Nullable
+ private static GetCredentialsRequest createProviderRequest(List<String> providerCapabilities,
+ android.credentials.GetCredentialRequest clientRequest,
+ String clientCallingPackage) {
+ List<GetCredentialOption> filteredOptions = new ArrayList<>();
+ for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
+ if (providerCapabilities.contains(option.getType())) {
+ Log.i(TAG, "In createProviderRequest - capability found : "
+ + option.getType());
+ filteredOptions.add(option);
+ } else {
+ Log.i(TAG, "In createProviderRequest - capability not "
+ + "found : " + option.getType());
+ }
+ }
+ if (!filteredOptions.isEmpty()) {
+ return new GetCredentialsRequest.Builder(clientCallingPackage).setGetCredentialOptions(
+ filteredOptions).build();
+ }
+ Log.i(TAG, "In createProviderRequest - returning null");
+ return null;
+ }
+
+ public ProviderGetSession(Context context,
+ CredentialProviderInfo info,
+ ProviderInternalCallback callbacks,
+ int userId, RemoteCredentialService remoteCredentialService,
+ GetCredentialsRequest request) {
+ super(context, info, request, callbacks, userId, remoteCredentialService);
+ setStatus(Status.PENDING);
}
/** Returns the credential entry maintained in state by this provider session. */
@Nullable
public CredentialEntry getCredentialEntry(@NonNull String entryId) {
- return mUiCredentials.get(entryId);
- }
-
- /** Returns the action entry maintained in state by this provider session. */
- @Nullable
- public Action getAction(@NonNull String entryId) {
- return mUiActions.get(entryId);
+ return mUiCredentialEntries.get(entryId);
}
/** Called when the provider response has been updated by an external source. */
- @Override
+ @Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
- updateResponse(response);
+ onUpdateResponse(response);
}
/** Called when the provider response resulted in a failure. */
- @Override
+ @Override // Callback from the remote provider
public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
updateStatusAndInvokeCallback(toStatus(errorCode));
}
/** Called when provider service dies. */
- @Override
+ @Override // Callback from the remote provider
public void onProviderServiceDied(RemoteCredentialService service) {
if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
@@ -116,77 +150,202 @@
}
}
- @Override
- protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
- if (!ProviderSession.isCompletionStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData not complete");
-
- throw new IllegalStateException("Status must be in completion mode");
+ @Override // Selection call from the request provider
+ protected void onUiEntrySelected(String entryType, String entryKey,
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ switch (entryType) {
+ case CREDENTIAL_ENTRY_KEY:
+ CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
+ if (credentialEntry == null) {
+ Log.i(TAG, "Credential entry not found");
+ //TODO: Handle properly
+ return;
+ }
+ onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
+ break;
+ case ACTION_ENTRY_KEY:
+ Action actionEntry = mUiActionsEntries.get(entryKey);
+ if (actionEntry == null) {
+ Log.i(TAG, "Action entry not found");
+ //TODO: Handle properly
+ return;
+ }
+ onActionEntrySelected(providerPendingIntentResponse);
+ break;
+ case AUTHENTICATION_ACTION_ENTRY_KEY:
+ if (mUiAuthenticationAction.first.equals(entryKey)) {
+ onAuthenticationEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Authentication entry not found");
+ }
+ break;
+ case REMOTE_ENTRY_KEY:
+ if (mUiRemoteEntry.first.equals(entryKey)) {
+ onRemoteEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Remote entry not found");
+ }
+ break;
+ default:
+ Log.i(TAG, "Unsupported entry type selected");
}
- GetCredentialsResponse response = getResponse();
- if (response == null) {
- Log.i(TAG, "In prepareUiData response null");
+ }
+ @Override // Call from request session to data to be shown on the UI
+ @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+ Log.i(TAG, "In prepareUiData");
+ if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+ Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
+ + mComponentName.flattenToString());
+ return null;
+ }
+ if (mProviderResponse == null) {
+ Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
}
- if (response.getAuthenticationAction() != null) {
- Log.i(TAG, "In prepareUiData auth not null");
-
- return prepareUiProviderDataWithAuthentication(response.getAuthenticationAction());
+ if (mProviderResponse.getAuthenticationAction() != null) {
+ Log.i(TAG, "In prepareUiData - top level authentication mode");
+ return prepareUiProviderData(null, null,
+ prepareUiAuthenticationAction(mProviderResponse.getAuthenticationAction()),
+ /*remoteEntry=*/null);
}
- if (response.getCredentialsDisplayContent() != null){
- Log.i(TAG, "In prepareUiData credentials not null");
-
- return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
+ if (mProviderResponse.getCredentialsDisplayContent() != null) {
+ Log.i(TAG, "In prepareUiData displayContent not null");
+ return prepareUiProviderData(prepareUiActionEntries(
+ mProviderResponse.getCredentialsDisplayContent().getActions()),
+ prepareUiCredentialEntries(mProviderResponse.getCredentialsDisplayContent()
+ .getCredentialEntries()),
+ /*authenticationAction=*/null,
+ prepareUiRemoteEntry(mProviderResponse
+ .getCredentialsDisplayContent().getRemoteCredentialEntry()));
}
return null;
}
- /**
- * To be called by {@link ProviderGetSession} when the UI is to be invoked.
- */
- @Nullable
- private GetCredentialProviderData prepareUiProviderDataWithCredentials(@NonNull
- CredentialsDisplayContent content) {
- Log.i(TAG, "in prepareUiProviderData");
- List<Entry> credentialEntries = new ArrayList<>();
- List<Entry> actionChips = new ArrayList<>();
- Entry authenticationEntry = null;
+ private Entry prepareUiRemoteEntry(Action remoteCredentialEntry) {
+ if (remoteCredentialEntry == null) {
+ return null;
+ }
+ String entryId = generateEntryId();
+ Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice());
+ mUiRemoteEntry = new Pair<>(entryId, remoteCredentialEntry);
+ return remoteEntry;
+ }
+
+ private Entry prepareUiAuthenticationAction(@NonNull Action authenticationAction) {
+ String entryId = generateEntryId();
+ Entry authEntry = new Entry(
+ AUTHENTICATION_ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice(),
+ authenticationAction.getPendingIntent(), /*fillInIntent=*/null);
+ mUiAuthenticationAction = new Pair<>(entryId, authenticationAction);
+ return authEntry;
+ }
+
+ private List<Entry> prepareUiCredentialEntries(@NonNull
+ List<CredentialEntry> credentialEntries) {
+ Log.i(TAG, "in prepareUiProviderDataWithCredentials");
+ List<Entry> credentialUiEntries = new ArrayList<>();
// Populate the credential entries
- for (CredentialEntry credentialEntry : content.getCredentialEntries()) {
- String entryId = UUID.randomUUID().toString();
- mUiCredentials.put(entryId, credentialEntry);
+ for (CredentialEntry credentialEntry : credentialEntries) {
+ String entryId = generateEntryId();
+ mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
- Slice slice = credentialEntry.getSlice();
- // TODO : Remove conversion of string to int after change in Entry class
- credentialEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
- credentialEntry.getSlice()));
+ if (credentialEntry.getPendingIntent() != null) {
+ credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+ credentialEntry.getSlice(), credentialEntry.getPendingIntent(),
+ /*fillInIntent=*/null));
+ } else if (credentialEntry.getCredential() != null) {
+ credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+ credentialEntry.getSlice()));
+ } else {
+ Log.i(TAG, "No credential or pending intent. Should not happen.");
+ }
}
- // populate the action chip
- for (Action action : content.getActions()) {
- String entryId = UUID.randomUUID().toString();
- mUiActions.put(entryId, action);
- // TODO : Remove conversion of string to int after change in Entry class
- actionChips.add(new Entry(ACTION_ENTRY_KEY, entryId,
- action.getSlice()));
- }
+ return credentialUiEntries;
+ }
- return new GetCredentialProviderData.Builder(mComponentName.flattenToString())
+ private List<Entry> prepareUiActionEntries(@Nullable List<Action> actions) {
+ List<Entry> actionEntries = new ArrayList<>();
+ for (Action action : actions) {
+ String entryId = UUID.randomUUID().toString();
+ mUiActionsEntries.put(entryId, action);
+ // TODO : Remove conversion of string to int after change in Entry class
+ actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice(),
+ action.getPendingIntent(), /*fillInIntent=*/null));
+ }
+ return actionEntries;
+ }
+
+ private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
+ List<Entry> credentialEntries, Entry authenticationActionEntry,
+ Entry remoteEntry) {
+ return new GetCredentialProviderData.Builder(
+ mComponentName.flattenToString()).setActionChips(actionEntries)
.setCredentialEntries(credentialEntries)
- .setActionChips(actionChips)
- .setAuthenticationEntry(authenticationEntry)
+ .setAuthenticationEntry(authenticationActionEntry)
+ .setRemoteEntry(remoteEntry)
.build();
}
- /**
- * To be called by {@link ProviderGetSession} when the UI is to be invoked.
- */
- @Nullable
- private GetCredentialProviderData prepareUiProviderDataWithAuthentication(@NonNull
- Action authenticationEntry) {
- // TODO : Implement authentication flow
- return null;
+ private void onCredentialEntrySelected(CredentialEntry credentialEntry,
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ if (credentialEntry.getCredential() != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName, new GetCredentialResponse(
+ credentialEntry.getCredential()));
+ return;
+ } else if (providerPendingIntentResponse != null) {
+ if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
+ Credential credential = PendingIntentResultHandler.extractCredential(
+ providerPendingIntentResponse.getResultData());
+ if (credential != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName,
+ new GetCredentialResponse(credential));
+ return;
+ }
+ }
+ // TODO: Handle other pending intent statuses
+ }
+ Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+ // TODO: Propagate failure to client
+ }
+
+ private void onAuthenticationEntrySelected(
+ @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+ if (providerPendingIntentResponse != null) {
+ if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
+ CredentialsDisplayContent content = PendingIntentResultHandler
+ .extractCredentialsDisplayContent(providerPendingIntentResponse
+ .getResultData());
+ if (content != null) {
+ onUpdateResponse(GetCredentialsResponse.createWithDisplayContent(content));
+ return;
+ }
+ }
+ //TODO: Other provider intent statuses
+ }
+ Log.i(TAG, "Display content not present in pending intent result");
+ // TODO: Propagate error to client
+ }
+
+ private void onActionEntrySelected(ProviderPendingIntentResponse
+ providerPendingIntentResponse) {
+ //TODO: Implement if any result expected after an action
+ }
+
+
+ /** Updates the response being maintained in state by this provider session. */
+ private void onUpdateResponse(GetCredentialsResponse response) {
+ mProviderResponse = response;
+ if (response.getAuthenticationAction() != null) {
+ Log.i(TAG , "updateResponse with authentication entry");
+ updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
+ } else if (response.getCredentialsDisplayContent() != null) {
+ Log.i(TAG , "updateResponse with credentialEntries");
+ // TODO validate response
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+ }
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 3a9f964..4a07f0a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -17,67 +17,130 @@
package com.android.server.credentials;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.Credential;
import android.credentials.ui.ProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.Action;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
+import android.util.Pair;
+
+import java.util.UUID;
/**
* Provider session storing the state of provider response and ui entries.
- * @param <T> The request type expected from the remote provider, for a given request session.
+ * @param <T> The request to be sent to the provider
+ * @param <R> The response to be expected from the provider
*/
-public abstract class ProviderSession<T> implements RemoteCredentialService.ProviderCallbacks<T> {
- // Key to be used as the entry key for an action entry
- protected static final String ACTION_ENTRY_KEY = "action_key";
+public abstract class ProviderSession<T, R>
+ implements RemoteCredentialService.ProviderCallbacks<R> {
+ // Key to be used as an entry key for a remote entry
+ protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
+ @NonNull protected final Context mContext;
@NonNull protected final ComponentName mComponentName;
@NonNull protected final CredentialProviderInfo mProviderInfo;
@NonNull protected final RemoteCredentialService mRemoteCredentialService;
@NonNull protected final int mUserId;
@NonNull protected Status mStatus = Status.NOT_STARTED;
@NonNull protected final ProviderInternalCallback mCallbacks;
+ @Nullable protected Credential mFinalCredentialResponse;
+ @NonNull protected final T mProviderRequest;
+ @Nullable protected R mProviderResponse;
+ @Nullable protected Pair<String, Action> mUiRemoteEntry;
+
+ /**
+ * Returns true if the given status reflects that the provider state is ready to be shown
+ * on the credMan UI.
+ */
+ public static boolean isUiInvokingStatus(Status status) {
+ return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
+ || status == Status.REQUIRES_AUTHENTICATION;
+ }
+
+ /**
+ * Returns true if the given status reflects that the provider is waiting for a remote
+ * response.
+ */
+ public static boolean isStatusWaitingForRemoteResponse(Status status) {
+ return status == Status.PENDING;
+ }
+
+ /**
+ * Returns true if the given status means that the provider session must be terminated.
+ */
+ public static boolean isTerminatingStatus(Status status) {
+ return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+ }
+
+ /**
+ * Returns true if the given status reflects that the provider is done getting the response,
+ * and is ready to return the final credential back to the user.
+ */
+ public static boolean isCompletionStatus(Status status) {
+ return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
+ || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
+ }
/**
* Interface to be implemented by any class that wishes to get a callback when a particular
* provider session's status changes. Typically, implemented by the {@link RequestSession}
* class.
+ * @param <V> the type of the final response expected
*/
- public interface ProviderInternalCallback {
- /**
- * Called when status changes.
- */
+ public interface ProviderInternalCallback<V> {
+ /** Called when status changes. */
void onProviderStatusChanged(Status status, ComponentName componentName);
+
+ /** Called when the final credential to be returned to the client has been received. */
+ void onFinalResponseReceived(ComponentName componentName, V response);
}
- protected ProviderSession(@NonNull CredentialProviderInfo info,
+ protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
+ @NonNull T providerRequest,
@NonNull ProviderInternalCallback callbacks,
@NonNull int userId,
@NonNull RemoteCredentialService remoteCredentialService) {
+ mContext = context;
mProviderInfo = info;
+ mProviderRequest = providerRequest;
mCallbacks = callbacks;
mUserId = userId;
mComponentName = info.getServiceInfo().getComponentName();
mRemoteCredentialService = remoteCredentialService;
}
- /** Update the response state stored with the provider session. */
- protected abstract void updateResponse (T response);
-
- /** Update the response state stored with the provider session. */
- protected abstract T getResponse ();
-
- /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
- * shown on the UI. */
- protected abstract ProviderData prepareUiData();
-
/** Provider status at various states of the request session. */
+ // TODO: Review status values, and adjust where needed
enum Status {
NOT_STARTED,
PENDING,
REQUIRES_AUTHENTICATION,
- COMPLETE,
+ CREDENTIALS_RECEIVED,
SERVICE_DEAD,
- CANCELED
+ CREDENTIAL_RECEIVED_FROM_INTENT,
+ PENDING_INTENT_INVOKED,
+ CREDENTIAL_RECEIVED_FROM_SELECTION,
+ SAVE_ENTRIES_RECEIVED, CANCELED
+ }
+
+ /** Converts exception to a provider session status. */
+ @NonNull
+ public static Status toStatus(
+ @CredentialProviderException.CredentialProviderError int errorCode) {
+ // TODO : Add more mappings as more flows are supported
+ return Status.CANCELED;
+ }
+
+ protected String generateEntryId() {
+ return UUID.randomUUID().toString();
+ }
+
+ public Credential getFinalCredentialResponse() {
+ return mFinalCredentialResponse;
}
protected void setStatus(@NonNull Status status) {
@@ -94,31 +157,37 @@
return mComponentName;
}
+ @NonNull
+ protected RemoteCredentialService getRemoteCredentialService() {
+ return mRemoteCredentialService;
+ }
+
/** Updates the status .*/
protected void updateStatusAndInvokeCallback(@NonNull Status status) {
setStatus(status);
mCallbacks.onProviderStatusChanged(status, mComponentName);
}
- @NonNull
- public static Status toStatus(
- @CredentialProviderException.CredentialProviderError int errorCode) {
- // TODO : Add more mappings as more flows are supported
- return Status.CANCELED;
+ protected void onRemoteEntrySelected(
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ //TODO: Implement
}
- /**
- * Returns true if the given status means that the provider session must be terminated.
- */
- public static boolean isTerminatingStatus(Status status) {
- return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+ /** Get the request to be sent to the provider. */
+ protected T getProviderRequest() {
+ return mProviderRequest;
}
- /**
- * Returns true if the given status means that the provider is done getting the response,
- * and is ready for user interaction.
- */
- public static boolean isCompletionStatus(Status status) {
- return status == Status.COMPLETE || status == Status.REQUIRES_AUTHENTICATION;
+ /** Update the response state stored with the provider session. */
+ @Nullable protected R getProviderResponse() {
+ return mProviderResponse;
}
+
+ /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
+ * shown on the UI. */
+ @Nullable protected abstract ProviderData prepareUiData();
+
+ /** Should be overridden to handle the selected entry from the UI. */
+ protected abstract void onUiEntrySelected(String entryType, String entryId,
+ ProviderPendingIntentResponse providerPendingIntentResponse);
}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index d0b6e7d..c2464b5 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,11 +24,14 @@
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CreateCredentialResponse;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderException.CredentialProviderError;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
+import android.service.credentials.ICreateCredentialCallback;
import android.service.credentials.ICredentialProviderService;
import android.service.credentials.IGetCredentialsCallback;
import android.text.format.DateUtils;
@@ -76,7 +79,7 @@
public RemoteCredentialService(@NonNull Context context,
@NonNull ComponentName componentName, int userId) {
super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
- .setComponent(componentName), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+ .setComponent(componentName), /*bindingFlags=*/0,
userId, ICredentialProviderService.Stub::asInterface);
mComponentName = componentName;
}
@@ -101,7 +104,7 @@
* provider service.
* @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
- * {@link ProviderSession} class that maintains provider state
+ * {@link ProviderGetSession} class that maintains provider state
*/
public void onGetCredentials(@NonNull GetCredentialsRequest request,
ProviderCallbacks<GetCredentialsResponse> callback) {
@@ -114,21 +117,21 @@
CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
ICancellationSignal cancellationSignal =
service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
- @Override
- public void onSuccess(GetCredentialsResponse response) {
- Log.i(TAG, "In onSuccess in RemoteCredentialService");
- getCredentials.complete(response);
- }
+ @Override
+ public void onSuccess(GetCredentialsResponse response) {
+ Log.i(TAG, "In onSuccess in RemoteCredentialService");
+ getCredentials.complete(response);
+ }
- @Override
- public void onFailure(@CredentialProviderError int errorCode,
- CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
- String errorMsg = message == null ? "" : String.valueOf(message);
- getCredentials.completeExceptionally(new CredentialProviderException(
- errorCode, errorMsg));
- }
- });
+ @Override
+ public void onFailure(@CredentialProviderError int errorCode,
+ CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(message);
+ getCredentials.completeExceptionally(new CredentialProviderException(
+ errorCode, errorMsg));
+ }
+ });
CompletableFuture<GetCredentialsResponse> future = futureRef.get();
if (future != null && future.isCancelled()) {
dispatchCancellationSignal(cancellationSignal);
@@ -137,38 +140,91 @@
}
return getCredentials;
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
- futureRef.set(connectThenExecute);
- connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> {
- if (error == null) {
- Log.i(TAG, "In RemoteCredentialService execute error is null");
- callback.onProviderResponseSuccess(result);
+ futureRef.set(connectThenExecute);
+ connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+ handleExecutionResponse(result, error, cancellationSink, callback)));
+ }
+
+ /** Main entry point to be called for executing a createCredential call on the remote
+ * provider service.
+ * @param request the request to be sent to the provider
+ * @param callback the callback to be used to send back the provider response to the
+ * {@link ProviderCreateSession} class that maintains provider state
+ */
+ public void onCreateCredential(@NonNull CreateCredentialRequest request,
+ ProviderCallbacks<CreateCredentialResponse> callback) {
+ Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
+ AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<CreateCredentialResponse>> futureRef =
+ new AtomicReference<>();
+
+ CompletableFuture<CreateCredentialResponse> connectThenExecute = postAsync(service -> {
+ CompletableFuture<CreateCredentialResponse> createCredentialFuture =
+ new CompletableFuture<>();
+ ICancellationSignal cancellationSignal = service.onCreateCredential(
+ request, new ICreateCredentialCallback.Stub() {
+ @Override
+ public void onSuccess(CreateCredentialResponse response) {
+ Log.i(TAG, "In onSuccess onCreateCredential "
+ + "in RemoteCredentialService");
+ createCredentialFuture.complete(response);
+ }
+
+ @Override
+ public void onFailure(@CredentialProviderError int errorCode,
+ CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(message);
+ createCredentialFuture.completeExceptionally(
+ new CredentialProviderException(errorCode, errorMsg));
+ }});
+ CompletableFuture<CreateCredentialResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellationSignal);
} else {
- if (error instanceof TimeoutException) {
- Log.i(TAG, "In RemoteCredentialService execute error is timeout");
- dispatchCancellationSignal(cancellationSink.get());
- callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TIMEOUT,
- error.getMessage());
- } else if (error instanceof CancellationException) {
- Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
- dispatchCancellationSignal(cancellationSink.get());
- callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TASK_CANCELED,
- error.getMessage());
- } else if (error instanceof CredentialProviderException) {
- Log.i(TAG, "In RemoteCredentialService execute error is provider error");
- callback.onProviderResponseFailure(((CredentialProviderException) error)
- .getErrorCode(),
- error.getMessage());
- } else {
- Log.i(TAG, "In RemoteCredentialService execute error is unknown");
- callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_UNKNOWN,
- error.getMessage());
- }
+ cancellationSink.set(cancellationSignal);
}
- }));
+ return createCredentialFuture;
+ }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+ futureRef.set(connectThenExecute);
+ connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+ handleExecutionResponse(result, error, cancellationSink, callback)));
+ }
+
+ private <T> void handleExecutionResponse(T result,
+ Throwable error,
+ AtomicReference<ICancellationSignal> cancellationSink,
+ ProviderCallbacks<T> callback) {
+ if (error == null) {
+ Log.i(TAG, "In RemoteCredentialService execute error is null");
+ callback.onProviderResponseSuccess(result);
+ } else {
+ if (error instanceof TimeoutException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is timeout");
+ dispatchCancellationSignal(cancellationSink.get());
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_TIMEOUT,
+ error.getMessage());
+ } else if (error instanceof CancellationException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
+ dispatchCancellationSignal(cancellationSink.get());
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_TASK_CANCELED,
+ error.getMessage());
+ } else if (error instanceof CredentialProviderException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is provider error");
+ callback.onProviderResponseFailure(((CredentialProviderException) error)
+ .getErrorCode(),
+ error.getMessage());
+ } else {
+ Log.i(TAG, "In RemoteCredentialService execute error is unknown");
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_UNKNOWN,
+ error.getMessage());
+ }
+ }
}
private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 1bacbb3..71fc67c 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -20,18 +20,29 @@
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
/**
* Base class of a request session, that listens to UI events. This class must be extended
* every time a new response type is expected from the providers.
*/
-abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback,
- ProviderSession.ProviderInternalCallback {
+abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback{
+ private static final String TAG = "RequestSession";
+
+ // TODO: Revise access levels of attributes
+ @NonNull protected final T mClientRequest;
+ @NonNull protected final U mClientCallback;
@NonNull protected final IBinder mRequestId;
@NonNull protected final Context mContext;
@NonNull protected final CredentialManagerUi mCredentialManagerUi;
@@ -39,29 +50,119 @@
@NonNull protected final Handler mHandler;
@NonNull protected boolean mIsFirstUiTurn = true;
@UserIdInt protected final int mUserId;
+ @NonNull protected final String mClientCallingPackage;
+
+ protected final Map<String, ProviderSession> mProviders = new HashMap<>();
protected RequestSession(@NonNull Context context,
- @UserIdInt int userId, @NonNull String requestType) {
+ @UserIdInt int userId, @NonNull T clientRequest, U clientCallback,
+ @NonNull String requestType,
+ String clientCallingPackage) {
mContext = context;
mUserId = userId;
+ mClientRequest = clientRequest;
+ mClientCallback = clientCallback;
mRequestType = requestType;
+ mClientCallingPackage = clientCallingPackage;
mHandler = new Handler(Looper.getMainLooper(), null, true);
mRequestId = new Binder();
mCredentialManagerUi = new CredentialManagerUi(mContext,
mUserId, this);
}
- /** Returns the unique identifier of this request session. */
- public IBinder getRequestId() {
- return mRequestId;
+ public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService);
+
+ protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList);
+
+ // UI callbacks
+
+ @Override // from CredentialManagerUiCallbacks
+ public void onUiSelection(UserSelectionDialogResult selection) {
+ String providerId = selection.getProviderId();
+ Log.i(TAG, "onUiSelection, providerId: " + providerId);
+ ProviderSession providerSession = mProviders.get(providerId);
+ if (providerSession == null) {
+ Log.i(TAG, "providerSession not found in onUiSelection");
+ return;
+ }
+ Log.i(TAG, "Provider session found");
+ providerSession.onUiEntrySelected(selection.getEntryKey(),
+ selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
}
- @Override // from CredentialManagerUiCallback
- public abstract void onUiSelection(UserSelectionDialogResult selection);
+ @Override // from CredentialManagerUiCallbacks
+ public void onUiCancellation() {
+ // User canceled the activity
+ finishSession();
+ }
- @Override // from CredentialManagerUiCallback
- public abstract void onUiCancelation();
+ protected void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ Log.i(TAG, "in onStatusChanged with status: " + status);
+ if (ProviderSession.isTerminatingStatus(status)) {
+ Log.i(TAG, "in onStatusChanged terminating status");
+ onProviderTerminated(componentName);
+ //TODO: Check if this was the provider we were waiting for and can invoke the UI now
+ } else if (ProviderSession.isCompletionStatus(status)) {
+ Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+ onProviderResponseComplete(componentName);
+ } else if (ProviderSession.isUiInvokingStatus(status)) {
+ Log.i(TAG, "in onStatusChanged isUiInvokingStatus status");
+ onProviderResponseRequiresUi();
+ }
+ }
- @Override // from ProviderInternalCallback
- public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
+ protected void onProviderTerminated(ComponentName componentName) {
+ //TODO: Implement
+ }
+
+ protected void onProviderResponseComplete(ComponentName componentName) {
+ //TODO: Implement
+ }
+
+ protected void onProviderResponseRequiresUi() {
+ Log.i(TAG, "in onProviderResponseComplete");
+ // TODO: Determine whether UI has already been invoked, and deal accordingly
+ if (!isAnyProviderPending()) {
+ Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
+ getProviderDataAndInitiateUi();
+ } else {
+ Log.i(TAG, "Can't invoke UI - waiting on some providers");
+ }
+ }
+
+ protected void finishSession() {
+ clearProviderSessions();
+ }
+
+ protected void clearProviderSessions() {
+ //TODO: Implement
+ mProviders.clear();
+ }
+
+ private boolean isAnyProviderPending() {
+ for (ProviderSession session : mProviders.values()) {
+ if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void getProviderDataAndInitiateUi() {
+ ArrayList<ProviderData> providerDataList = new ArrayList<>();
+ for (ProviderSession session : mProviders.values()) {
+ Log.i(TAG, "preparing data for : " + session.getComponentName());
+ ProviderData providerData = session.prepareUiData();
+ if (providerData != null) {
+ Log.i(TAG, "Provider data is not null");
+ providerDataList.add(providerData);
+ }
+ }
+ if (!providerDataList.isEmpty()) {
+ Log.i(TAG, "provider list not empty about to initiate ui");
+ launchUiWithProviderData(providerDataList);
+ }
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 70422bb..53ded69 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23,6 +23,7 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -46,6 +47,7 @@
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -259,6 +261,7 @@
import android.content.pm.Signature;
import android.content.pm.StringParceledListSlice;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -329,6 +332,7 @@
import android.util.AtomicFile;
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -645,6 +649,15 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long USE_SET_LOCATION_ENABLED = 117835097L;
+ /**
+ * Forces wipeDataNoLock to attempt removing the user or throw an error as
+ * opposed to trying to factory reset the device first and only then falling back to user
+ * removal.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long EXPLICIT_WIPE_BEHAVIOUR = 242193913L;
+
// Only add to the end of the list. Do not change or rearrange these values, that will break
// historical data. Do not use negative numbers or zero, logger only handles positive
// integers.
@@ -661,6 +674,17 @@
private @interface CopyAccountStatus {}
/**
+ * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+ * corresponding app-ops.
+ */
+ private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
+ new ArrayMap<>();
+ static {
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+ EXEMPT_FROM_APP_STANDBY, OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY);
+ }
+
+ /**
* Admin apps targeting Android S+ may not use
* {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
* on the {@code DevicePolicyManager} instance obtained by calling
@@ -710,8 +734,7 @@
* Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
* is requested for user u.
*/
- private final Set<Pair<String, Integer>> mPackagesToRemove =
- new ArraySet<Pair<String, Integer>>();
+ private final Set<UserPackage> mPackagesToRemove = new ArraySet<>();
final LocalService mLocalService;
@@ -6699,8 +6722,8 @@
}
@Override
- public void wipeDataWithReason(int flags, String wipeReasonForUser,
- boolean calledOnParentInstance) {
+ public void wipeDataWithReason(int flags, @NonNull String wipeReasonForUser,
+ boolean calledOnParentInstance, boolean factoryReset) {
if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
return;
}
@@ -6782,7 +6805,8 @@
"DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
adminName, calledByProfileOwnerOnOrgOwnedDevice);
- wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId);
+ wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
+ calledOnParentInstance, factoryReset);
}
private String getGenericWipeReason(
@@ -6844,8 +6868,13 @@
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
}
+ /**
+ * @param factoryReset null: legacy behaviour, false: attempt to remove user, true: attempt to
+ * factory reset
+ */
private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
- String wipeReasonForUser, int userId) {
+ @NonNull String wipeReasonForUser, int userId, boolean calledOnParentInstance,
+ @Nullable Boolean factoryReset) {
wtfIfInLock();
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -6863,7 +6892,39 @@
+ " restriction is set for user " + userId);
}
- if (userId == UserHandle.USER_SYSTEM) {
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean wipeDevice;
+ if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+ admin.getPackageName(),
+ userId)) {
+ // Legacy mode
+ wipeDevice = isSystemUser;
+ } else {
+ // Explicit behaviour
+ if (factoryReset) {
+ // TODO(b/254031494) Replace with new factory reset permission checks
+ boolean hasPermission = isDeviceOwnerUserId(userId)
+ || (isOrganizationOwnedDeviceWithManagedProfile()
+ && calledOnParentInstance);
+ Preconditions.checkState(hasPermission,
+ "Admin %s does not have permission to factory reset the device.",
+ userId);
+ wipeDevice = true;
+ } else {
+ Preconditions.checkCallAuthorization(!isSystemUser,
+ "User %s is a system user and cannot be removed", userId);
+ boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
+ && mUserManager.getAliveUsers().stream()
+ .filter((it) -> it.getUserHandle().getIdentifier() != userId)
+ .noneMatch(UserInfo::isFull);
+ Preconditions.checkState(!isLastNonHeadlessUser,
+ "Removing user %s would leave the device without any active users. "
+ + "Consider factory resetting the device instead.",
+ userId);
+ wipeDevice = false;
+ }
+ }
+ if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
internalReason,
@@ -7131,7 +7192,7 @@
}
@Override
- public void reportFailedPasswordAttempt(int userHandle) {
+ public void reportFailedPasswordAttempt(int userHandle, boolean parent) {
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
final CallerIdentity caller = getCallerIdentity();
@@ -7153,7 +7214,7 @@
saveSettingsLocked(userHandle);
if (mHasFeature) {
strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
- userHandle, /* parent */ false);
+ userHandle, /* parent= */ false);
int max = strictestAdmin != null
? strictestAdmin.maximumFailedPasswordsForWipe : 0;
if (max > 0 && policy.mFailedPasswordAttempts >= max) {
@@ -7183,10 +7244,13 @@
// IMPORTANT: Call without holding the lock to prevent deadlock.
try {
wipeDataNoLock(strictestAdmin.info.getComponent(),
- /*flags=*/ 0,
- /*reason=*/ "reportFailedPasswordAttempt()",
+ /* flags= */ 0,
+ /* reason= */ "reportFailedPasswordAttempt()",
getFailedPasswordAttemptWipeMessage(),
- userId);
+ userId,
+ /* calledOnParentInstance= */ parent,
+ // factoryReset=null to enable U- behaviour
+ /* factoryReset= */ null);
} catch (SecurityException e) {
Slogf.w(LOG_TAG, "Failed to wipe user " + userId
+ " after max failed password attempts reached.", e);
@@ -7195,7 +7259,7 @@
if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
- /*result*/ 0, /*method strength*/ 1);
+ /* result= */ 0, /* method strength= */ 1);
}
}
@@ -13982,7 +14046,6 @@
@Override
public boolean isProvisioningAllowed(String action, String packageName) {
Objects.requireNonNull(packageName);
-
final CallerIdentity caller = getCallerIdentity();
final long ident = mInjector.binderClearCallingIdentity();
try {
@@ -13994,21 +14057,21 @@
mInjector.binderRestoreCallingIdentity(ident);
}
- return checkProvisioningPreconditionSkipPermission(action, packageName) == STATUS_OK;
+ return checkProvisioningPreconditionSkipPermission(action, packageName, caller.getUserId())
+ == STATUS_OK;
}
@Override
public int checkProvisioningPrecondition(String action, String packageName) {
Objects.requireNonNull(packageName, "packageName is null");
-
+ final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
- return checkProvisioningPreconditionSkipPermission(action, packageName);
+ return checkProvisioningPreconditionSkipPermission(action, packageName, caller.getUserId());
}
-
private int checkProvisioningPreconditionSkipPermission(String action,
- String packageName) {
+ String packageName, int userId) {
if (!mHasFeature) {
logMissingFeatureAction("Cannot check provisioning for action " + action);
return STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
@@ -14016,7 +14079,8 @@
if (!isProvisioningAllowed()) {
return STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
}
- final int code = checkProvisioningPreConditionSkipPermissionNoLog(action, packageName);
+ final int code = checkProvisioningPreConditionSkipPermissionNoLog(
+ action, packageName, userId);
if (code != STATUS_OK) {
Slogf.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName
+ ") failed: "
@@ -14043,15 +14107,14 @@
}
private int checkProvisioningPreConditionSkipPermissionNoLog(String action,
- String packageName) {
- final int callingUserId = mInjector.userHandleGetCallingUserId();
+ String packageName, int userId) {
if (action != null) {
switch (action) {
case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
- return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
+ return checkManagedProfileProvisioningPreCondition(packageName, userId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
- return checkDeviceOwnerProvisioningPreCondition(callingUserId);
+ return checkDeviceOwnerProvisioningPreCondition(userId);
}
}
throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -15044,7 +15107,7 @@
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
- Pair<String, Integer> packageUserPair = new Pair<>(packageName, caller.getUserId());
+ UserPackage packageUserPair = UserPackage.of(caller.getUserId(), packageName);
synchronized (getLockObject()) {
return mPackagesToRemove.contains(packageUserPair);
}
@@ -15072,7 +15135,7 @@
throw new IllegalArgumentException("Cannot uninstall a package with a device owner");
}
- final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+ final UserPackage packageUserPair = UserPackage.of(userId, packageName);
synchronized (getLockObject()) {
mPackagesToRemove.add(packageUserPair);
}
@@ -15132,7 +15195,7 @@
}
private void startUninstallIntent(final String packageName, final int userId) {
- final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+ final UserPackage packageUserPair = UserPackage.of(userId, packageName);
synchronized (getLockObject()) {
if (!mPackagesToRemove.contains(packageUserPair)) {
// Do nothing if uninstall was not requested or was already started.
@@ -16969,6 +17032,88 @@
});
}
+ @Override
+ public void setApplicationExemptions(String packageName, int[] exemptions) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+ Objects.requireNonNull(exemptions, "Application exemptions must not be null.");
+ Preconditions.checkArgument(areApplicationExemptionsValid(exemptions),
+ "Invalid application exemption constant found in application exemptions set.");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+ final CallerIdentity caller = getCallerIdentity();
+ final ApplicationInfo packageInfo;
+ packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+ for (Map.Entry<Integer, String> entry :
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+ int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+ entry.getValue(), packageInfo.uid, packageInfo.packageName);
+ int newMode = ArrayUtils.contains(exemptions, entry.getKey())
+ ? MODE_ALLOWED : MODE_DEFAULT;
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ if (currentMode != newMode) {
+ mInjector.getAppOpsManager()
+ .setMode(entry.getValue(),
+ packageInfo.uid,
+ packageName,
+ newMode);
+ }
+ });
+ }
+ }
+
+ @Override
+ public int[] getApplicationExemptions(String packageName) {
+ if (!mHasFeature) {
+ return new int[0];
+ }
+ Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+ final CallerIdentity caller = getCallerIdentity();
+ final ApplicationInfo packageInfo;
+ packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+ IntArray appliedExemptions = new IntArray(0);
+ for (Map.Entry<Integer, String> entry :
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+ if (mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+ entry.getValue(), packageInfo.uid, packageInfo.packageName) == MODE_ALLOWED) {
+ appliedExemptions.add(entry.getKey());
+ }
+ }
+ return appliedExemptions.toArray();
+ }
+
+ private ApplicationInfo getPackageInfoWithNullCheck(String packageName, CallerIdentity caller) {
+ final ApplicationInfo packageInfo =
+ mInjector.getPackageManagerInternal().getApplicationInfo(
+ packageName,
+ /* flags= */ 0,
+ caller.getUid(),
+ caller.getUserId());
+ if (packageInfo == null) {
+ throw new ServiceSpecificException(
+ DevicePolicyManager.ERROR_PACKAGE_NAME_NOT_FOUND,
+ "Package name not found.");
+ }
+ return packageInfo;
+ }
+
+ private boolean areApplicationExemptionsValid(int[] exemptions) {
+ for (int exemption : exemptions) {
+ if (!APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.containsKey(exemption)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private boolean isCallingFromPackage(String packageName, int callingUid) {
return mInjector.binderWithCleanCallingIdentity(() -> {
try {
@@ -17622,7 +17767,7 @@
final long identity = Binder.clearCallingIdentity();
try {
final int result = checkProvisioningPreconditionSkipPermission(
- ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName());
+ ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName(), caller.getUserId());
if (result != STATUS_OK) {
throw new ServiceSpecificException(
ERROR_PRE_CONDITION_FAILED,
@@ -18032,7 +18177,8 @@
final long identity = Binder.clearCallingIdentity();
try {
int result = checkProvisioningPreconditionSkipPermission(
- ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
+ ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName(),
+ caller.getUserId());
if (result != STATUS_OK) {
throw new ServiceSpecificException(
ERROR_PRE_CONDITION_FAILED,
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index 46e59a9..c3329795 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -83,9 +83,10 @@
Slogf.d(TAG, "Creating initial user");
t.traceBegin("create-initial-user");
try {
+ int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
// TODO(b/204091126): proper name for user
UserInfo newUser = um.createUserEvenWhenDisallowed("Real User",
- UserManager.USER_TYPE_FULL_SECONDARY, UserInfo.FLAG_ADMIN,
+ UserManager.USER_TYPE_FULL_SECONDARY, flags,
/* disallowedPackages= */ null, /* token= */ null);
Slogf.i(TAG, "Created initial user: %s", newUser.toFullString());
initialUserId = newUser.id;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 88c0ec6..9089bf7 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -103,6 +103,7 @@
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockSettingsInternal;
import com.android.server.am.ActivityManagerService;
import com.android.server.ambientcontext.AmbientContextManagerService;
import com.android.server.appbinding.AppBindingService;
@@ -464,6 +465,7 @@
private final long mRuntimeStartUptime;
private static final String START_HIDL_SERVICES = "StartHidlServices";
+ private static final String START_SENSOR_MANAGER_SERVICE = "StartISensorManagerService";
private static final String START_BLOB_STORE_SERVICE = "startBlobStoreManagerService";
private static final String SYSPROP_START_COUNT = "sys.system_server.start_count";
@@ -482,6 +484,9 @@
/** Start the IStats services. This is a blocking call and can take time. */
private static native void startIStatsService();
+ /** Start the ISensorManager service. This is a blocking call and can take time. */
+ private static native void startISensorManagerService();
+
/**
* Start the memtrack proxy service.
*/
@@ -1567,11 +1572,18 @@
wm.onInitReady();
t.traceEnd();
- // Start receiving calls from HIDL services. Start in in a separate thread
+ // Start receiving calls from SensorManager services. Start in a separate thread
// because it need to connect to SensorManager. This has to start
// after PHASE_WAIT_FOR_SENSOR_SERVICE is done.
SystemServerInitThreadPool.submit(() -> {
TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
+ traceLog.traceBegin(START_SENSOR_MANAGER_SERVICE);
+ startISensorManagerService();
+ traceLog.traceEnd();
+ }, START_SENSOR_MANAGER_SERVICE);
+
+ SystemServerInitThreadPool.submit(() -> {
+ TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(START_HIDL_SERVICES);
startHidlServices();
traceLog.traceEnd();
@@ -1665,7 +1677,18 @@
// Bring up services needed for UI.
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
t.traceBegin("StartInputMethodManagerLifecycle");
- mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
+ String immsClassName = context.getResources().getString(
+ R.string.config_deviceSpecificInputMethodManagerService);
+ if (immsClassName.isEmpty()) {
+ mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
+ } else {
+ try {
+ Slog.i(TAG, "Starting custom IMMS: " + immsClassName);
+ mSystemServiceManager.startService(immsClassName);
+ } catch (Throwable e) {
+ reportWtf("starting " + immsClassName, e);
+ }
+ }
t.traceEnd();
t.traceBegin("StartAccessibilityManagerService");
@@ -1786,17 +1809,18 @@
dpms = mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
t.traceEnd();
- if (!isWatch) {
- t.traceBegin("StartStatusBarManagerService");
- try {
- statusBar = new StatusBarManagerService(context);
- ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
- DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
- } catch (Throwable e) {
- reportWtf("starting StatusBarManagerService", e);
+ t.traceBegin("StartStatusBarManagerService");
+ try {
+ statusBar = new StatusBarManagerService(context);
+ if (!isWatch) {
+ statusBar.publishGlobalActionsProvider();
}
- t.traceEnd();
+ ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
+ DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
+ } catch (Throwable e) {
+ reportWtf("starting StatusBarManagerService", e);
}
+ t.traceEnd();
if (deviceHasConfigString(context,
R.string.config_defaultMusicRecognitionService)) {
@@ -3008,6 +3032,14 @@
t.traceEnd();
}, t);
+ t.traceBegin("LockSettingsThirdPartyAppsStarted");
+ LockSettingsInternal lockSettingsInternal =
+ LocalServices.getService(LockSettingsInternal.class);
+ if (lockSettingsInternal != null) {
+ lockSettingsInternal.onThirdPartyAppsStarted();
+ }
+ t.traceEnd();
+
t.traceBegin("StartSystemUI");
try {
startSystemUi(context, windowManagerF);
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index eab3b77..292320e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -53,6 +53,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -372,7 +373,8 @@
@Override
public boolean equals(Object o) {
ListenerKey key = (ListenerKey) o;
- return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId
+ return key.getPackageName().equals(mPackageName)
+ && Objects.equals(key.getUserId(), mUserId)
&& key.getShortcutId().equals(mShortcutId);
}
diff --git a/services/permission/Android.bp b/services/permission/Android.bp
new file mode 100644
index 0000000..b03f17b
--- /dev/null
+++ b/services/permission/Android.bp
@@ -0,0 +1,40 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "services.permission-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.kt",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.permission",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.permission-sources"],
+ libs: [
+ "services.core",
+ // Soong fails to automatically add this dependency because all the
+ // *.kt sources are inside a filegroup.
+ "kotlin-annotations",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ jarjar_rules: "jarjar-rules.txt",
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-Xno-call-assertions",
+ "-Xno-param-assertions",
+ "-Xno-receiver-assertions",
+ ],
+}
diff --git a/services/permission/OWNERS b/services/permission/OWNERS
new file mode 100644
index 0000000..6c6c9fc
--- /dev/null
+++ b/services/permission/OWNERS
@@ -0,0 +1,4 @@
+ashfall@google.com
+joecastro@google.com
+ntmyren@google.com
+zhanghai@google.com
diff --git a/services/permission/jarjar-rules.txt b/services/permission/jarjar-rules.txt
new file mode 100644
index 0000000..34af3af
--- /dev/null
+++ b/services/permission/jarjar-rules.txt
@@ -0,0 +1 @@
+rule kotlin.** com.android.server.permission.jarjar.@0
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt
similarity index 68%
copy from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
copy to services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt
index 817c209f..21ec159 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery
+package com.android.server.permission
-import com.android.settingslib.spa.framework.EntryProvider
+import com.android.internal.annotations.Keep
+import com.android.server.pm.permission.PermissionManagerServiceInterface
-class GalleryEntryProvider : EntryProvider()
+/**
+ * Modern implementation of [PermissionManagerServiceInterface].
+ */
+@Keep
+class ModernPermissionManagerServiceImpl
diff --git a/services/proguard_permission.flags b/services/proguard_permission.flags
new file mode 100644
index 0000000..15edc61
--- /dev/null
+++ b/services/proguard_permission.flags
@@ -0,0 +1,9 @@
+# Only shrink services.permission classes.
+# Note that while more aggressive services shrinking is enabled by default (see proguard.flags), for
+# cases where that's not yet possible, we still need to shrink the permission package to prune out
+# unused Kotlin stdlib dependencies.
+-keep class !com.android.server.permission.** { *; }
+
+# CoverageService guards optional jacoco class references with a runtime guard, so we can safely
+# suppress build-time warnings.
+-dontwarn org.jacoco.agent.rt.*
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 3fbc400..640bde3 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -244,7 +244,7 @@
.setCurrentMethodVisible();
}
verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
- .showSoftInput(any(), anyInt(), any());
+ .showSoftInput(any(), any(), anyInt(), any());
}
protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
@@ -254,6 +254,6 @@
.setCurrentMethodNotVisible();
}
verify(mMockInputMethod, times(hideSoftInput ? 1 : 0))
- .hideSoftInput(any(), anyInt(), any());
+ .hideSoftInput(any(), any(), anyInt(), any());
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index e4d124e..55645d7 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -219,6 +219,20 @@
printState(mock(Computer::class.java), mock(IndentingPrintWriter::class.java),
null, null)
},
+ service(Type.QUERENT, "printOwnersForPackage") {
+ printOwnersForPackage(
+ mock(IndentingPrintWriter::class.java),
+ it.targetPackageName,
+ it.userId
+ )
+ },
+ service(Type.QUERENT, "printOwnersForDomains") {
+ printOwnersForDomains(
+ mock(IndentingPrintWriter::class.java),
+ listOf("example.com"),
+ it.userId
+ )
+ },
service(Type.VERIFIER, "setStatus") {
setDomainVerificationStatus(
it.targetDomainSetId,
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 73b1907c..681bfcf 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -56,6 +56,7 @@
"service-jobscheduler",
"service-permission.impl",
"service-sdksandbox.impl",
+ "services.backup",
"services.companion",
"services.core",
"services.devicepolicy",
diff --git a/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java
new file mode 100644
index 0000000..33275bd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java
@@ -0,0 +1,91 @@
+/*
+ * 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.server;
+
+import android.util.Dumpable;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@code JUnit} rule that logs (using tag {@value #TAG} the contents of
+ * {@link Dumpable dumpables} in case of failure.
+ */
+public final class DumpableDumperRule implements TestRule {
+
+ private static final String TAG = DumpableDumperRule.class.getSimpleName();
+
+ private static final String[] NO_ARGS = {};
+
+ private final List<Dumpable> mDumpables = new ArrayList<>();
+
+ /**
+ * Adds a {@link Dumpable} to be logged if the test case fails.
+ */
+ public void addDumpable(Dumpable dumpable) {
+ mDumpables.add(dumpable);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } catch (Throwable t) {
+ dumpOnFailure(description.getMethodName());
+ throw t;
+ }
+ }
+ };
+ }
+
+ private void dumpOnFailure(String testName) throws IOException {
+ if (mDumpables.isEmpty()) {
+ return;
+ }
+ Log.w(TAG, "Dumping " + mDumpables.size() + " dumpables on failure of " + testName);
+ mDumpables.forEach(d -> logDumpable(d));
+ }
+
+ private void logDumpable(Dumpable dumpable) {
+ try {
+ try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+ dumpable.dump(pw, NO_ARGS);
+ String[] dump = sw.toString().split(System.lineSeparator());
+ Log.w(TAG, "Dumping " + dumpable.getDumpableName() + " (" + dump.length
+ + " lines):");
+ for (String line : dump) {
+ Log.w(TAG, line);
+ }
+
+ } catch (RuntimeException e) {
+ Log.e(TAG, "RuntimeException dumping " + dumpable.getDumpableName(), e);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "IOException dumping " + dumpable.getDumpableName(), e);
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
index 9aa28ce..693a96d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
@@ -21,8 +21,13 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.google.common.truth.Expect;
+import com.google.common.truth.StandardSubjectBuilder;
+
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -38,6 +43,14 @@
private MockitoSession mSession;
+ protected final Expect mExpect = Expect.create();
+ protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
+
+ @Rule
+ public final RuleChain mTwoRingsOfPowerAndOneChainToRuleThemAll = RuleChain
+ .outerRule(mDumpableDumperRule)
+ .around(mExpect);
+
@Before
public void startSession() {
if (DEBUG) {
@@ -78,4 +91,12 @@
mSession.finishMocking();
}
}
+
+ protected final StandardSubjectBuilder expectWithMessage(String msg) {
+ return mExpect.withMessage(msg);
+ }
+
+ protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
+ return mExpect.withMessage(format, args);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0f7c0d7..2f6b07b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -466,7 +466,7 @@
@Test
public void testRunnableAt_Cached_Interactive() {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_INTERACTIVE);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index fd605f7..b9615f6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -50,7 +50,6 @@
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -964,8 +963,8 @@
// First broadcast should have already been dead
verifyScheduleRegisteredReceiver(receiverApp, airplane);
- verify(receiverApp).scheduleCrashLocked(any(),
- eq(CannotDeliverBroadcastException.TYPE_ID), any());
+ // The old receiverApp should be killed gently
+ assertTrue(receiverApp.isKilled());
// Second broadcast in new process should work fine
final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
@@ -995,8 +994,8 @@
// First broadcast should have already been dead
verifyScheduleReceiver(receiverApp, airplane);
- verify(receiverApp).scheduleCrashLocked(any(),
- eq(CannotDeliverBroadcastException.TYPE_ID), any());
+ // The old receiverApp should be killed gently
+ assertTrue(receiverApp.isKilled());
// Second broadcast in new process should work fine
final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index d78f6d83..24e5175 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -1507,6 +1507,39 @@
}
@Test
+ public void testSwitchUser() {
+ mockManageUsersGranted();
+ mockModifyGameModeGranted();
+
+ mockDeviceConfigBattery();
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper(), context.getFilesDir());
+ startUser(gameManagerService, USER_ID_1);
+ startUser(gameManagerService, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY);
+ assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+ GameManager.GAME_MODE_BATTERY);
+
+ mockDeviceConfigAll();
+ switchUser(gameManagerService, USER_ID_1, USER_ID_2);
+ assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_2),
+ GameManager.GAME_MODE_STANDARD);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+
+ switchUser(gameManagerService, USER_ID_2, USER_ID_1);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ }
+
+ @Test
public void testUpdateResolutionScalingFactor() {
mockModifyGameModeGranted();
mockDeviceConfigBattery();
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index e1713b0..98e895a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -22,6 +22,7 @@
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_WIFI_SCAN;
import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
@@ -127,6 +128,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -137,6 +140,8 @@
.update();
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -151,6 +156,8 @@
.update();
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -169,6 +176,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -183,6 +192,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -197,6 +208,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -211,6 +224,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -314,6 +329,8 @@
.update();
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -328,6 +345,8 @@
.update();
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -403,6 +422,8 @@
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -418,6 +439,8 @@
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -433,6 +456,8 @@
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
new file mode 100644
index 0000000..f535997
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.server.backup;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupAndRestoreFeatureFlagsTest {
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Test
+ public void getBackupTransportFutureTimeoutMillis_notSet_returnsDefault() {
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+ 600000);
+ }
+
+ @Test
+ public void getBackupTransportFutureTimeoutMillis_set_returnsSetValue() {
+ DeviceConfig.setProperty("backup_and_restore", "backup_transport_future_timeout_millis",
+ "1234", false);
+
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+ 1234);
+ }
+
+ @Test
+ public void getBackupTransportCallbackTimeoutMillis_notSet_returnsDefault() {
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+ 300000);
+ }
+
+ @Test
+ public void getBackupTransportCallbackTimeoutMillis_set_returnsSetValue() {
+ DeviceConfig.setProperty("backup_and_restore", "backup_transport_callback_timeout_millis",
+ "5678", false);
+
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+ 5678);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
new file mode 100644
index 0000000..d99779e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 20af02e..4c28c51 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -33,6 +33,7 @@
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.test.TestLooper;
import android.util.FloatProperty;
@@ -135,6 +136,16 @@
DisplayPowerCallbacks displayPowerCallbacks) {
return mWakelockController;
}
+
+ @Override
+ DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+ WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+ Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+ SensorManager sensorManager) {
+ return new DisplayPowerProximityStateController(wakelockController,
+ displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+ sensorManager, /* injector= */ null);
+ }
};
addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 0000000..6e91b24
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,407 @@
+/*
+ * 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.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+ @Mock
+ WakelockController mWakelockController;
+
+ @Mock
+ DisplayDeviceConfig mDisplayDeviceConfig;
+
+ @Mock
+ Runnable mNudgeUpdatePowerState;
+
+ @Mock
+ SensorManager mSensorManager;
+
+ private Sensor mProximitySensor;
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private SensorEventListener mSensorEventListener;
+ private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+ @Before
+ public void before() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ // This is kept null because currently there is no way to define a sensor
+ // name in TestUtils
+ name = null;
+ }
+ });
+ setUpProxSensor();
+ DisplayPowerProximityStateController.Injector injector =
+ new DisplayPowerProximityStateController.Injector() {
+ @Override
+ DisplayPowerProximityStateController.Clock createClock() {
+ return new DisplayPowerProximityStateController.Clock() {
+ @Override
+ public long uptimeMillis() {
+ return mClock.now();
+ }
+ };
+ }
+ };
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, 0,
+ mSensorManager, injector);
+ mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+ // Set the system to pending wait for proximity
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+ // Will not wait or be in the pending wait state of not already pending
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+ throws Exception {
+ // Set the system to the state where it will ignore proximity unless changed
+ enableProximitySensor();
+ emitAndValidatePositiveProximityEvent();
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ advanceTime(1);
+ assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ verify(mNudgeUpdatePowerState, times(2)).run();
+
+ // Do not set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Set the system to pending wait for proximity. But because the proximity is being
+ // ignored, it will not wait or not set the pending wait
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void cleanupDisablesTheProximitySensor() {
+ enableProximitySensor();
+ mDisplayPowerProximityStateController.cleanup();
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsFalseWhenNotAvailable() {
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = null;
+ name = null;
+ }
+ });
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, 1,
+ mSensorManager, null);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+ DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+ when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ Sensor newProxSensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(newProxSensor));
+ mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+ updatedDisplayDeviceConfig);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+ // Doesn't do anything not asked to wait
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ false));
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Sets pending wait negative proximity if not already waiting
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Will not set pending wait negative proximity if already waiting
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ }
+
+ @Test
+ public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+ }
+
+ @Test
+ public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+ // Start ignoring proximity sensor
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ assertTrue(
+ mDisplayPowerProximityStateController
+ .shouldSkipRampBecauseOfProximityChangeToNegative());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+ }
+
+ @Test
+ public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+ throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ private void setUpProxSensor() throws Exception {
+ mProximitySensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(mProximitySensor));
+ }
+
+ private void emitAndValidatePositiveProximityEvent() throws Exception {
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+ verify(mSensorManager).registerListener(mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ verify(mNudgeUpdatePowerState).run();
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ // Call evaluateProximityState with the request for using the proximity sensor. This will
+ // register the proximity sensor listener, which will be needed for mocking positive
+ // proximity scenarios.
+ private void enableProximitySensor() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verifyZeroInteractions(mWakelockController);
+ }
+
+ private void setScreenOffBecauseOfPositiveProximityState() {
+ // Prepare a request to indicate that the proximity sensor is to be used
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+
+ Runnable onProximityPositiveRunnable = mock(Runnable.class);
+ when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+ onProximityPositiveRunnable);
+
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 2547347..6e4d214 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -17,6 +17,7 @@
package com.android.server.job;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -34,14 +35,20 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
+import android.app.IActivityManager;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -49,8 +56,11 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
@@ -98,12 +108,23 @@
@Mock
private IPackageManager mIPackageManager;
- static class InjectorForTest extends JobConcurrencyManager.Injector {
+ private static class InjectorForTest extends JobConcurrencyManager.Injector {
+ public final ArrayMap<JobServiceContext, JobStatus> contexts = new ArrayMap<>();
+
@Override
JobServiceContext createJobServiceContext(JobSchedulerService service,
JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
JobPackageTracker tracker, Looper looper) {
- return mock(JobServiceContext.class);
+ final JobServiceContext context = mock(JobServiceContext.class);
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ Object[] args = invocationOnMock.getArguments();
+ final JobStatus job = (JobStatus) args[0];
+ contexts.put(context, job);
+ doReturn(job).when(context).getRunningJobLocked();
+ return true;
+ }).when(context).executeRunnableJob(any(), anyInt());
+ contexts.put(context, null);
+ return context;
}
}
@@ -135,13 +156,22 @@
R.bool.config_jobSchedulerRestrictBackgroundUser);
when(mContext.getResources()).thenReturn(mResources);
doReturn(mContext).when(jobSchedulerService).getTestableContext();
+ doReturn(jobSchedulerService).when(jobSchedulerService).getLock();
mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build())
.when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER)));
mPendingJobQueue = new PendingJobQueue();
doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+ doReturn(mock(PowerManager.class)).when(mContext).getSystemService(PowerManager.class);
mInjector = new InjectorForTest();
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ Object[] args = invocationOnMock.getArguments();
+ final JobStatus job = (JobStatus) args[0];
+ return job.shouldTreatAsExpeditedJob()
+ ? JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS
+ : JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
+ }).when(jobSchedulerService).getMinJobExecutionGuaranteeMs(any());
mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService, mInjector);
mGracePeriodObserver = mock(GracePeriodObserver.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -150,6 +180,16 @@
createCurrentUser(true);
mNextUserId = 10;
mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver;
+
+ IActivityManager activityManager = ActivityManager.getService();
+ spyOn(activityManager);
+ try {
+ doNothing().when(activityManager).registerUserSwitchObserver(any(), anyString());
+ } catch (RemoteException e) {
+ fail("registerUserSwitchObserver threw exception: " + e.getMessage());
+ }
+
+ mJobConcurrencyManager.onSystemReady();
}
@After
@@ -167,32 +207,93 @@
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
- mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.numRunningTopEj);
}
@Test
public void testPrepareForAssignmentDetermination_onlyPendingJobs() {
- final ArraySet<JobStatus> jobs = new ArraySet<>();
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
mPendingJobQueue.add(job);
- jobs.add(job);
}
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
- mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.numRunningTopEj);
+ }
+
+ @Test
+ public void testPrepareForAssignmentDetermination_onlyPreferredUidOnly() {
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+ JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+ mJobConcurrencyManager.addRunningJobForTesting(job);
+ }
+
+ for (int i = 0; i < mInjector.contexts.size(); ++i) {
+ doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+
+ assertEquals(0, idle.size());
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+ assertEquals(0, stoppable.size());
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.numRunningTopEj);
+ }
+
+ @Test
+ public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+ JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+ job.startedAsExpeditedJob = true;
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ mJobConcurrencyManager.addRunningJobForTesting(job);
+ }
+
+ for (int i = 0; i < mInjector.contexts.size(); ++i) {
+ doReturn(i % 2 == 0).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+
+ assertEquals(0, idle.size());
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, preferredUidOnly.size());
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+ assignmentInfo.numRunningTopEj);
}
@Test
@@ -213,10 +314,13 @@
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
- mJobConcurrencyManager
- .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable);
+ .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+ assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
for (int i = changed.size() - 1; i >= 0; --i) {
@@ -226,6 +330,175 @@
}
@Test
+ public void testDetermineAssignments_allPreferredUidOnly_shortTimeLeft() throws Exception {
+ mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+ setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+ new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
+ final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+ final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+ setPackageUid(sourcePkgName, uid);
+ final JobStatus job = createJob(uid, sourcePkgName);
+ spyOn(job);
+ doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+ if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+ mJobConcurrencyManager.addRunningJobForTesting(job);
+ } else {
+ mPendingJobQueue.add(job);
+ }
+ }
+
+ // Waiting time is too short, so we shouldn't create any extra contexts.
+ final long remainingTimeMs = JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS / 2;
+ for (int i = 0; i < mInjector.contexts.size(); ++i) {
+ doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+ doReturn(remainingTimeMs)
+ .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+ assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+ mJobConcurrencyManager
+ .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+ assignmentInfo);
+
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+ assertEquals(0, changed.size());
+ }
+
+ @Test
+ public void testDetermineAssignments_allPreferredUidOnly_mediumTimeLeft() throws Exception {
+ mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+ setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+ new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+ final ArraySet<JobStatus> jobs = new ArraySet<>();
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
+ final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+ final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+ setPackageUid(sourcePkgName, uid);
+ final JobStatus job = createJob(uid, sourcePkgName);
+ spyOn(job);
+ doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+ if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+ mJobConcurrencyManager.addRunningJobForTesting(job);
+ } else {
+ mPendingJobQueue.add(job);
+ jobs.add(job);
+ }
+ }
+
+ // Waiting time is longer than the EJ waiting time, but shorter than regular job waiting
+ // time, so we should only create an extra context for an EJ.
+ final long remainingTimeMs = (JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS
+ + JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS) / 2;
+ for (int i = 0; i < mInjector.contexts.size(); ++i) {
+ doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+ doReturn(remainingTimeMs)
+ .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+ assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+ mJobConcurrencyManager
+ .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+ assignmentInfo);
+
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+ for (int i = changed.size() - 1; i >= 0; --i) {
+ jobs.remove(changed.valueAt(i).newJob);
+ }
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - 1, jobs.size());
+ assertEquals(1, changed.size());
+ JobStatus assignedJob = changed.valueAt(0).newJob;
+ assertTrue(assignedJob.shouldTreatAsExpeditedJob());
+ }
+
+ @Test
+ public void testDetermineAssignments_allPreferredUidOnly_longTimeLeft() throws Exception {
+ mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+ setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+ new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+ final ArraySet<JobStatus> jobs = new ArraySet<>();
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
+ final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+ final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+ setPackageUid(sourcePkgName, uid);
+ final JobStatus job = createJob(uid, sourcePkgName);
+ spyOn(job);
+ doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+ if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+ mJobConcurrencyManager.addRunningJobForTesting(job);
+ } else {
+ mPendingJobQueue.add(job);
+ jobs.add(job);
+ }
+ }
+
+ // Waiting time is longer than even the regular job waiting time, so we should
+ // create an extra context for an EJ, and potentially one for a regular job.
+ final long remainingTimeMs = 2 * JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS;
+ for (int i = 0; i < mInjector.contexts.size(); ++i) {
+ doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+ doReturn(remainingTimeMs)
+ .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+ assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+ mJobConcurrencyManager
+ .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+ assignmentInfo);
+
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+ // Depending on iteration order, we may create 1 or 2 contexts.
+ final long numAssignedJobs = changed.size();
+ assertTrue(numAssignedJobs > 0);
+ assertTrue(numAssignedJobs <= 2);
+ for (int i = 0; i < numAssignedJobs; ++i) {
+ jobs.remove(changed.valueAt(i).newJob);
+ }
+ assertEquals(numAssignedJobs,
+ JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - jobs.size());
+ JobStatus firstAssignedJob = changed.valueAt(0).newJob;
+ if (!firstAssignedJob.shouldTreatAsExpeditedJob()) {
+ assertEquals(2, numAssignedJobs);
+ assertTrue(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
+ } else if (numAssignedJobs == 2) {
+ assertFalse(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
+ }
+ }
+
+ @Test
public void testIsPkgConcurrencyLimited_top() {
final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
new file mode 100644
index 0000000..1a4a7bd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
@@ -0,0 +1,184 @@
+/*
+ * 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.server.pm;
+
+import static org.junit.Assert.fail;
+
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
+
+import com.google.common.truth.Expect;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@link UserVisibilityListener} implementation that expects callback events to be asynchronously
+ * received.
+ */
+public final class AsyncUserVisibilityListener implements UserVisibilityListener {
+
+ private static final String TAG = AsyncUserVisibilityListener.class.getSimpleName();
+
+ private static final long WAIT_TIMEOUT_MS = 2_000;
+ private static final long WAIT_NO_EVENTS_TIMEOUT_MS = 1_000;
+
+ private static int sNextId;
+
+ private final Object mLock = new Object();
+ private final Expect mExpect;
+ private final int mId = ++sNextId;
+ private final Thread mExpectedReceiverThread;
+ private final CountDownLatch mLatch;
+ private final List<UserVisibilityChangedEvent> mExpectedEvents;
+
+ @GuardedBy("mLock")
+ private final List<UserVisibilityChangedEvent> mReceivedEvents = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private final List<String> mErrors = new ArrayList<>();
+
+ private AsyncUserVisibilityListener(Expect expect, Thread expectedReceiverThread,
+ List<UserVisibilityChangedEvent> expectedEvents) {
+ mExpect = expect;
+ mExpectedReceiverThread = expectedReceiverThread;
+ mExpectedEvents = expectedEvents;
+ mLatch = new CountDownLatch(expectedEvents.size());
+ }
+
+ @Override
+ public void onUserVisibilityChanged(int userId, boolean visible) {
+ UserVisibilityChangedEvent event = new UserVisibilityChangedEvent(userId, visible);
+ Thread callingThread = Thread.currentThread();
+ Log.d(TAG, "Received event (" + event + ") on thread " + callingThread);
+
+ if (callingThread != mExpectedReceiverThread) {
+ addError("event %s received in on thread %s but was expected on thread %s",
+ event, callingThread, mExpectedReceiverThread);
+ }
+ synchronized (mLock) {
+ mReceivedEvents.add(event);
+ mLatch.countDown();
+ }
+ }
+
+ /**
+ * Verifies the expected events were called.
+ */
+ public void verify() throws InterruptedException {
+ waitForEventsAndCheckErrors();
+
+ List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
+
+ if (receivedEvents.isEmpty()) {
+ mExpect.withMessage("received events").that(receivedEvents).isEmpty();
+ return;
+ }
+
+ // NOTE: check "inOrder" might be too harsh in some cases (for example, if the fg user
+ // has 2 profiles, the order of the events on the profiles wouldn't matter), but we
+ // still need some dependency (like "user A became invisible before user B became
+ // visible", so this is fine for now (but eventually we might need to add more
+ // sophisticated assertions)
+ mExpect.withMessage("received events").that(receivedEvents)
+ .containsExactlyElementsIn(mExpectedEvents).inOrder();
+ }
+
+ @Override
+ public String toString() {
+ List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
+ return "[" + getClass().getSimpleName() + ": id=" + mId
+ + ", creationThread=" + mExpectedReceiverThread
+ + ", received=" + receivedEvents.size()
+ + ", events=" + receivedEvents + "]";
+ }
+
+ private List<UserVisibilityChangedEvent> getReceivedEvents() {
+ synchronized (mLock) {
+ return Collections.unmodifiableList(mReceivedEvents);
+ }
+ }
+
+ private void waitForEventsAndCheckErrors() throws InterruptedException {
+ waitForEvents();
+ synchronized (mLock) {
+ if (!mErrors.isEmpty()) {
+ fail(mErrors.size() + " errors on received events: " + mErrors);
+ }
+ }
+ }
+
+ private void waitForEvents() throws InterruptedException {
+ if (mExpectedEvents.isEmpty()) {
+ Log.v(TAG, "Sleeping " + WAIT_NO_EVENTS_TIMEOUT_MS + "ms to make sure no event is "
+ + "received");
+ Thread.sleep(WAIT_NO_EVENTS_TIMEOUT_MS);
+ return;
+ }
+
+ int expectedNumberEvents = mExpectedEvents.size();
+ Log.v(TAG, "Waiting up to " + WAIT_TIMEOUT_MS + "ms until " + expectedNumberEvents
+ + " events are received");
+ if (!mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
+ addError("Timed out (%d ms) waiting for %d events; received %d so far (%s), "
+ + "but expecting %d (%s)", WAIT_NO_EVENTS_TIMEOUT_MS, expectedNumberEvents,
+ receivedEvents.size(), receivedEvents, expectedNumberEvents, mExpectedEvents);
+ }
+ }
+
+ @SuppressWarnings("AnnotateFormatMethod")
+ private void addError(String format, Object...args) {
+ synchronized (mLock) {
+ mErrors.add(String.format(format, args));
+ }
+ }
+
+ /**
+ * Factory for {@link AsyncUserVisibilityListener} objects.
+ */
+ public static final class Factory {
+ private final Expect mExpect;
+ private final Thread mExpectedReceiverThread;
+
+ public Factory(Expect expect, Thread expectedReceiverThread) {
+ mExpect = expect;
+ mExpectedReceiverThread = expectedReceiverThread;
+ }
+
+ /**
+ * Creates a {@link AsyncUserVisibilityListener} that is expecting the given events.
+ */
+ public AsyncUserVisibilityListener forEvents(UserVisibilityChangedEvent...expectedEvents) {
+ return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
+ Arrays.asList(expectedEvents));
+ }
+
+ /**
+ * Creates a {@link AsyncUserVisibilityListener} that is expecting no events.
+ */
+ public AsyncUserVisibilityListener forNoEvents() {
+ return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
+ Collections.emptyList());
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 7ccd6d9..e0662c4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -51,6 +51,7 @@
mUserManagerInternal = rule.mocks().injector.userManagerInternal
whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1))
+ whenever(mUserManagerInternal.getUserTypesForStatsd(any())).thenReturn(intArrayOf(1, 1))
mPms = createPackageManagerService()
doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any())
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java
new file mode 100644
index 0000000..58a265b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.pm;
+
+import android.annotation.UserIdInt;
+
+/**
+ * Representation of a {@link UserManagerInternal.UserVisibilityListener} event.
+ */
+public final class UserVisibilityChangedEvent {
+
+ public @UserIdInt int userId;
+ public boolean visible;
+
+ UserVisibilityChangedEvent(@UserIdInt int userId, boolean visible) {
+ this.userId = userId;
+ this.visible = visible;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + userId;
+ result = prime * result + (visible ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ UserVisibilityChangedEvent other = (UserVisibilityChangedEvent) obj;
+ if (userId != other.userId) return false;
+ if (visible != other.visible) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return userId + ":" + (visible ? "visible" : "invisible");
+ }
+
+ /**
+ * Factory method.
+ */
+ public static UserVisibilityChangedEvent onVisible(@UserIdInt int userId) {
+ return new UserVisibilityChangedEvent(userId, /* visible= */ true);
+ }
+
+ /**
+ * Factory method.
+ */
+ public static UserVisibilityChangedEvent onInvisible(@UserIdInt int userId) {
+ return new UserVisibilityChangedEvent(userId, /* visible= */ false);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 21f541f..c5a8572 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -15,15 +15,14 @@
*/
package com.android.server.pm;
-import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertThrows;
-
-import android.util.Log;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
import org.junit.Test;
@@ -36,268 +35,123 @@
*/
public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
- private static final String TAG = UserVisibilityMediatorMUMDTest.class.getSimpleName();
-
- public UserVisibilityMediatorMUMDTest() {
+ public UserVisibilityMediatorMUMDTest() throws Exception {
super(/* usersOnSecondaryDisplaysEnabled= */ true);
}
@Test
- public void testAssignUserToDisplay_systemUser() {
- assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
+ public void testStartFgUser_onInvalidDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
+
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ listener.verify();
}
@Test
- public void testAssignUserToDisplay_invalidDisplay() {
- assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
+ public void testStartBgUser_onInvalidDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, INVALID_DISPLAY);
+
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(USER_ID);
+
+ listener.verify();
}
@Test
- public void testAssignUserToDisplay_currentUser() {
- mockCurrentUser(USER_ID);
+ public void testStartBgUser_onSecondaryDisplay_displayAvailable() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
- assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
- assertNoUserAssignedToDisplay();
+ expectUserIsVisible(USER_ID);
+ expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+ listener.verify();
}
@Test
- public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
+ public void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser()
+ throws Exception {
startDefaultProfile();
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ // Make sure they were visible before
+ expectUserIsVisibleOnDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
- Log.v(TAG, "Exception: " + e);
- assertNoUserAssignedToDisplay();
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsNotVisibleOnDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
}
@Test
- public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- stopDefaultProfile();
+ public void testStartBgUser_onSecondaryDisplay_displayAlreadyAssigned() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
+ startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
- Log.v(TAG, "Exception: " + e);
- assertNoUserAssignedToDisplay();
+ expectUserIsNotVisibleAtAll(USER_ID);
+ expectNoDisplayAssignedToUser(USER_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, OTHER_USER_ID);
+
+ listener.verify();
}
@Test
- public void testAssignUserToDisplay_displayAvailable() {
- mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ public void testStartBgUser_onSecondaryDisplay_userAlreadyAssigned() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+ startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
- assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsVisible(USER_ID);
+ expectUserIsVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, USER_ID);
+
+ listener.verify();
}
@Test
- public void testAssignUserToDisplay_displayAlreadyAssigned() {
- mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ public void testStartBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
- IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mMediator.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
- Log.v(TAG, "Exception: " + e);
- assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
- .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
- + USER_ID + ".*");
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+ listener.verify();
}
-
- @Test
- public void testAssignUserToDisplay_userAlreadyAssigned() {
- mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
- .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
- + SECONDARY_DISPLAY_ID + ".*");
-
- assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
- addDefaultProfileAndParent();
-
- mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
- addDefaultProfileAndParent();
-
- mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
- addDefaultProfileAndParent();
-
- mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testUnassignUserFromDisplay() {
- testAssignUserToDisplay_displayAvailable();
-
- mMediator.unassignUserFromDisplay(USER_ID);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testIsUserVisible_bgUserOnSecondaryDisplay() {
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID)
- .that(mMediator.isUserVisible(USER_ID)).isTrue();
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // isUserVisible() for bg users relies only on the user / display assignments
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserUnassignedSecondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplayAssignedToAnotherUser() {
- mockCurrentUser(USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- addDefaultProfileAndParent();
- startDefaultProfile();
- mockCurrentUser(PARENT_USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- addDefaultProfileAndParent();
- stopDefaultProfile();
- mockCurrentUser(PARENT_USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() {
- addDefaultProfileAndParent();
- startDefaultProfile();
- mockCurrentUser(PARENT_USER_ID);
-
- // TODO(b/244644281): change it to isFalse() once isUserVisible() is fixed (see note there)
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_bgUserOnSecondaryDisplay() {
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_bgUserOnAnotherSecondaryDisplay() {
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // the tests for isUserVisible(userId, display) for non-current users relies on the explicit
- // user / display assignments
- // TODO(b/244644281): add such tests if the logic change
-
- @Test
- public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() {
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(mMediator.getDisplayAssignedToUser(USER_ID))
- .isEqualTo(SECONDARY_DISPLAY_ID);
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // getDisplayAssignedToUser() for bg users relies only on the user / display assignments
-
- @Test
- public void testGetUserAssignedToDisplay_bgUserOnSecondaryDisplay() {
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_noUserOnSecondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
- // started on secondary display if its parent isn't, so we might need to remove (or refactor
- // this test) if/when the underlying logic changes
- @Test
- public void testGetUserAssignedToDisplay_profileOnSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(USER_ID);
- assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // getUserAssignedToDisplay() for bg users relies only on the user / display assignments
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 7ae8117..fc0287f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -15,9 +15,7 @@
*/
package com.android.server.pm;
-import static android.os.UserHandle.USER_SYSTEM;
-
-import static org.junit.Assert.assertThrows;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import org.junit.Test;
@@ -35,41 +33,18 @@
}
@Test
- public void testAssignUserToDisplay_otherDisplay_currentUser() {
- mockCurrentUser(USER_ID);
+ public void testStartBgUser_onSecondaryDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
- assertThrows(UnsupportedOperationException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
- }
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
- @Test
- public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- startDefaultProfile();
+ expectUserIsNotVisibleAtAll(USER_ID);
+ expectNoDisplayAssignedToUser(USER_ID);
- assertThrows(UnsupportedOperationException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
- }
+ expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
- @Test
- public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- stopDefaultProfile();
-
- assertThrows(UnsupportedOperationException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testUnassignUserFromDisplay_ignored() {
- mockCurrentUser(USER_ID);
-
- mMediator.unassignUserFromDisplay(USER_SYSTEM);
- mMediator.unassignUserFromDisplay(USER_ID);
- mMediator.unassignUserFromDisplay(OTHER_USER_ID);
-
- assertNoUserAssignedToDisplay();
+ listener.verify();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 22e6e0d..17ee909 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -15,22 +15,40 @@
*/
package com.android.server.pm;
+import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_CURRENT;
+import static android.os.UserHandle.USER_CURRENT_OR_SELF;
import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
import static com.google.common.truth.Truth.assertWithMessage;
-import android.annotation.UserIdInt;
-import android.util.SparseIntArray;
+import static org.junit.Assert.assertThrows;
+import android.annotation.UserIdInt;
+import android.os.HandlerThread;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.ExtendedMockitoTestCase;
+
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.Arrays;
/**
* Base class for {@link UserVisibilityMediator} tests.
@@ -38,8 +56,39 @@
* <p>It contains common logics and tests for behaviors that should be invariant regardless of the
* device mode (for example, whether the device supports concurrent multiple users on multiple
* displays or not).
+ *
+ * <p><P>NOTE: <p> rather than adopting the "one test case for method approach", this class (and
+ * its subclass) adds "one test case for scenario" approach, so it can test many properties (if user
+ * is visible, display associated to the user, etc...) for each scenario (full user started on fg,
+ * profile user started on bg, etc...).
*/
-abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrInternalTestCase {
+abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
+
+ private static final String TAG = UserVisibilityMediatorTestCase.class.getSimpleName();
+
+ /**
+ * Id for a simple user (that doesn't have profiles).
+ */
+ protected static final int USER_ID = 600;
+
+ /**
+ * Id for another simple user.
+ */
+ protected static final int OTHER_USER_ID = 666;
+
+ /**
+ * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
+ *
+ * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+ */
+ protected static final int PARENT_USER_ID = 642;
+
+ /**
+ * Id for a profile whose parent is {@link #PARENTUSER_ID}.
+ *
+ * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+ */
+ protected static final int PROFILE_USER_ID = 643;
/**
* Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
@@ -51,12 +100,15 @@
*/
protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
- private final boolean mUsersOnSecondaryDisplaysEnabled;
+ protected static final boolean FG = true;
+ protected static final boolean BG = false;
- // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
- // details into the unit test, but it's fine for now as the tests were copied "as is" - it
- // would be better to use a geter() instead
- protected final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
+ private static final HandlerThread sHandlerThread = new HandlerThread(TAG);
+
+ protected final AsyncUserVisibilityListener.Factory mListenerFactory =
+ new AsyncUserVisibilityListener.Factory(mExpect, sHandlerThread);
+
+ private final boolean mUsersOnSecondaryDisplaysEnabled;
protected UserVisibilityMediator mMediator;
@@ -64,260 +116,463 @@
mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
}
- @Before
- public final void setMediator() {
- mMediator = new UserVisibilityMediator(mUms, mUsersOnSecondaryDisplaysEnabled,
- mUsersOnSecondaryDisplays);
+ @BeforeClass
+ public static final void startHandlerThread() {
+ Log.d(TAG, "Starting handler thread " + sHandlerThread);
+ sHandlerThread.start();
}
- @Test
- public final void testAssignUserToDisplay_defaultDisplayIgnored() {
- mMediator.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public final void testIsUserVisible_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_NULL)
- .that(mMediator.isUserVisible(USER_NULL)).isFalse();
- }
-
- @Test
- public final void testIsUserVisible_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID)
- .that(mMediator.isUserVisible(USER_ID)).isTrue();
- }
-
- @Test
- public final void testIsUserVisible_nonCurrentUser() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID)
- .that(mMediator.isUserVisible(USER_ID)).isFalse();
- }
-
- @Test
- public final void testIsUserVisible_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
- .that(mMediator.isUserVisible(PROFILE_USER_ID)).isTrue();
- }
-
- @Test
- public final void testIsUserVisible_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
- .that(mMediator.isUserVisible(PROFILE_USER_ID)).isFalse();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_NULL, DEFAULT_DISPLAY)
- .that(mMediator.isUserVisible(USER_NULL, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, INVALID_DISPLAY)
- .that(mMediator.isUserVisible(USER_ID, INVALID_DISPLAY)).isFalse();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
- .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
- .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testGetDisplayAssignedToUser_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
- .that(mMediator.getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
- }
-
- @Test
- public final void testGetDisplayAssignedToUser_nonCurrentUser() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
- .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
- .isEqualTo(DEFAULT_DISPLAY);
- }
-
- @Test
- public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
- .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
- .isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_invalidDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY)
- .that(mMediator.getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID);
- }
-
- @Test
- public final void testGetUserAssignedToDisplay_defaultDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY)
- .that(mMediator.getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID);
- }
-
- @Test
- public final void testGetUserAssignedToDisplay_secondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID))
- .isEqualTo(USER_ID);
- }
-
- // NOTE: should only called by tests that indirectly needs to check user assignments (like
- // isUserVisible), not by tests for the user assignment methods per se.
- protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
- mUsersOnSecondaryDisplays.put(userId, displayId);
- }
-
- protected final void assertNoUserAssignedToDisplay() {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
- .isEmpty();
- }
-
- protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
- .containsExactly(userId, displayId);
- }
-
- private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
- int size = mUsersOnSecondaryDisplays.size();
- Map<Integer, Integer> map = new LinkedHashMap<>(size);
- for (int i = 0; i < size; i++) {
- map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
+ @AfterClass
+ public static final void quitHandlerThread() {
+ Log.d(TAG, "Quitting handler thread " + sHandlerThread);
+ if (!sHandlerThread.quit()) {
+ Log.w(TAG, "sHandlerThread(" + sHandlerThread + ").quit() returned false");
}
- return map;
+ }
+
+ @Before
+ public final void setFixtures() {
+ mMediator = new UserVisibilityMediator(mUsersOnSecondaryDisplaysEnabled,
+ sHandlerThread.getThreadHandler());
+ mDumpableDumperRule.addDumpable(mMediator);
+ }
+
+ @Test
+ public final void testAssignUserToDisplayOnStart_invalidUserIds() {
+ assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplayOnStart(USER_NULL, USER_ID, FG, DEFAULT_DISPLAY));
+ assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplayOnStart(USER_ALL, USER_ID, FG, DEFAULT_DISPLAY));
+ assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplayOnStart(USER_CURRENT, USER_ID, FG, DEFAULT_DISPLAY));
+ assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplayOnStart(USER_CURRENT_OR_SELF, USER_ID, FG, DEFAULT_DISPLAY));
+ }
+
+ @Test
+ public final void testStartFgUser_onDefaultDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(USER_ID));
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(USER_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ // TODO(b/244644281): once isUserVisible() is fixed (see note there), this assertion will
+ // fail on MUMD, so we'll need to refactor / split this test (and possibly others)
+ expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testSwitchFgUser_onDefaultDisplay() throws Exception {
+ int previousCurrentUserId = OTHER_USER_ID;
+ int currentUserId = USER_ID;
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(previousCurrentUserId),
+ onInvisible(previousCurrentUserId),
+ onVisible(currentUserId));
+ startForegroundUser(previousCurrentUserId);
+
+ int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(currentUserId);
+ expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+ expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+ expectUserIsVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(currentUserId);
+
+ expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+ expectUserIsNotVisibleAtAll(previousCurrentUserId);
+ expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartFgUser_onSecondaryDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result =
+ mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(USER_ID);
+ expectNoDisplayAssignedToUser(USER_ID);
+ expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgUser_onDefaultDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+ expectUserIsNotVisibleAtAll(USER_ID);
+ expectNoDisplayAssignedToUser(USER_ID);
+ expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgSystemUser_onSecondaryDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(USER_ID));
+ // Must explicitly set current user, as USER_SYSTEM is the default current user
+ startForegroundUser(USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_SYSTEM, USER_SYSTEM, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(USER_SYSTEM);
+
+ expectNoDisplayAssignedToUser(USER_SYSTEM);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID));
+ startForegroundUser(PARENT_USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(PROFILE_USER_ID);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+ expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStopVisibleProfile() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID),
+ onInvisible(PROFILE_USER_ID));
+ startDefaultProfile();
+
+ mMediator.unassignUserFromDisplayOnStop(PROFILE_USER_ID);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testVisibleProfileBecomesInvisibleWhenParentIsSwitchedOut() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID),
+ onInvisible(PARENT_USER_ID),
+ onInvisible(PROFILE_USER_ID),
+ onVisible(OTHER_USER_ID));
+ startDefaultProfile();
+
+ startForegroundUser(OTHER_USER_ID);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, OTHER_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+ startBackgroundUser(PARENT_USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+ listener.verify();
+ }
+
+ // Not supported - profiles can only be started on default display
+ @Test
+ public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartFgProfile_onDefaultDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartFgProfile_onSecondaryDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testIsUserVisible_invalidUsers() throws Exception {
+ expectWithMessage("isUserVisible(%s)", USER_NULL)
+ .that(mMediator.isUserVisible(USER_NULL))
+ .isFalse();
+ expectWithMessage("isUserVisible(%s)", USER_NULL)
+ .that(mMediator.isUserVisible(USER_ALL))
+ .isFalse();
+ expectWithMessage("isUserVisible(%s)", USER_NULL)
+ .that(mMediator.isUserVisible(USER_CURRENT))
+ .isFalse();
+ expectWithMessage("isUserVisible(%s)", USER_NULL)
+ .that(mMediator.isUserVisible(USER_CURRENT_OR_SELF))
+ .isFalse();
+ }
+
+ @Test
+ public final void testRemoveListener() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ mMediator.removeListener(listener);
+
+ startForegroundUser(USER_ID);
+ listener.verify();
+ }
+
+ /**
+ * Starts a user in foreground on the default display, asserting it was properly started.
+ *
+ * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+ * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+ * se.
+ */
+ protected void startForegroundUser(@UserIdInt int userId) {
+ Log.d(TAG, "startForegroundUSer(" + userId + ")");
+ int result = mMediator.assignUserToDisplayOnStart(userId, userId, FG, DEFAULT_DISPLAY);
+ if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
+ throw new IllegalStateException("Failed to start foreground user " + userId
+ + ": mediator returned " + userAssignmentResultToString(result));
+ }
+ }
+
+ /**
+ * Starts a user in background on the default display, asserting it was properly started.
+ *
+ * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+ * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+ * se.
+ */
+ protected void startBackgroundUser(@UserIdInt int userId) {
+ Log.d(TAG, "startBackgroundUser(" + userId + ")");
+ int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, DEFAULT_DISPLAY);
+ if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) {
+ throw new IllegalStateException("Failed to start background user " + userId
+ + ": mediator returned " + userAssignmentResultToString(result));
+ }
+ }
+
+ /**
+ * Starts the {@link #PROFILE_USER_ID default profile} in background and its
+ * {@link #PARENT_USER_ID parent} in foreground on the main display, asserting that
+ * both were properly started.
+ *
+ * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+ * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+ * se.
+ */
+ protected void startDefaultProfile() {
+ startForegroundUser(PARENT_USER_ID);
+ Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
+ + " its parent (" + PARENT_USER_ID + ") on foreground");
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
+ throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
+ + ": mediator returned " + userAssignmentResultToString(result));
+ }
+ }
+
+ /**
+ * Starts a user in background on the secondary display, asserting it was properly started.
+ *
+ * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+ * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+ * se.
+ */
+ protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) {
+ Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY,
+ "must pass a secondary display, not %d", displayId);
+ Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
+ int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, displayId);
+ if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
+ throw new IllegalStateException("Failed to startuser " + userId
+ + " on background: mediator returned " + userAssignmentResultToString(result));
+ }
+ }
+
+ protected AsyncUserVisibilityListener addListenerForNoEvents() {
+ AsyncUserVisibilityListener listener = mListenerFactory.forNoEvents();
+ mMediator.addListener(listener);
+ return listener;
+ }
+
+ protected AsyncUserVisibilityListener addListenerForEvents(
+ UserVisibilityChangedEvent... events) {
+ AsyncUserVisibilityListener listener = mListenerFactory.forEvents(events);
+ mMediator.addListener(listener);
+ return listener;
+ }
+
+ protected void assertStartUserResult(int actualResult, int expectedResult) {
+ assertWithMessage("startUser() result (where %s=%s and %s=%s)",
+ expectedResult, userAssignmentResultToString(expectedResult),
+ actualResult, userAssignmentResultToString(actualResult))
+ .that(actualResult).isEqualTo(expectedResult);
+ }
+
+ protected void expectUserIsVisible(@UserIdInt int userId) {
+ expectWithMessage("mediator.isUserVisible(%s)", userId)
+ .that(mMediator.isUserVisible(userId))
+ .isTrue();
+ }
+
+ protected void expectVisibleUsers(@UserIdInt Integer... userIds) {
+ IntArray visibleUsers = mMediator.getVisibleUsers();
+ expectWithMessage("getVisibleUsers()").that(visibleUsers).isNotNull();
+ expectWithMessage("getVisibleUsers()").that(visibleUsers.toArray()).asList()
+ .containsExactlyElementsIn(Arrays.asList(userIds));
+ }
+
+ protected void expectUserIsVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+ expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+ .that(mMediator.isUserVisible(userId, displayId))
+ .isTrue();
+ }
+
+ protected void expectUserIsNotVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+ expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+ .that(mMediator.isUserVisible(userId, displayId))
+ .isFalse();
+ }
+
+ protected void expectUserIsNotVisibleAtAll(@UserIdInt int userId) {
+ expectWithMessage("mediator.isUserVisible(%s)", userId)
+ .that(mMediator.isUserVisible(userId))
+ .isFalse();
+ expectUserIsNotVisibleOnDisplay(userId, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(userId, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(userId, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay(userId, OTHER_SECONDARY_DISPLAY_ID);
+ }
+
+ protected void expectDisplayAssignedToUser(@UserIdInt int userId, int displayId) {
+ expectWithMessage("getDisplayAssignedToUser(%s)", userId)
+ .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(displayId);
+ }
+
+ protected void expectNoDisplayAssignedToUser(@UserIdInt int userId) {
+ expectWithMessage("getDisplayAssignedToUser(%s)", userId)
+ .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(INVALID_DISPLAY);
+ }
+
+ protected void expectUserAssignedToDisplay(int displayId, @UserIdInt int userId) {
+ expectWithMessage("getUserAssignedToDisplay(%s)", displayId)
+ .that(mMediator.getUserAssignedToDisplay(displayId)).isEqualTo(userId);
+ }
+
+ protected void expectNoUserAssignedToDisplay(int displayId) {
+ expectWithMessage("getUserAssignedToDisplay(%s)", displayId)
+ .that(mMediator.getUserAssignedToDisplay(displayId))
+ .isEqualTo(INITIAL_CURRENT_USER_ID);
}
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 61bb57e..9386a23 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -204,10 +204,10 @@
":FrameworksServicesTests_install_uses_sdk_q0",
":FrameworksServicesTests_install_uses_sdk_q0_r0",
":FrameworksServicesTests_install_uses_sdk_r0",
- ":FrameworksServicesTests_install_uses_sdk_r5",
+ ":FrameworksServicesTests_install_uses_sdk_r1000",
":FrameworksServicesTests_install_uses_sdk_r_none",
":FrameworksServicesTests_install_uses_sdk_r0_s0",
- ":FrameworksServicesTests_install_uses_sdk_r0_s5",
+ ":FrameworksServicesTests_install_uses_sdk_r0_s1000",
":FrameworksServicesTests_keyset_permdef_sa_unone",
":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6551bde..6349b21 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -107,6 +107,9 @@
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
+ <intent>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent>
</queries>
<!-- Uses API introduced in O (26) -->
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 9052f58..9c7ce83 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -33,6 +33,7 @@
<option name="test-file-name" value="SimpleServiceTestApp1.apk" />
<option name="test-file-name" value="SimpleServiceTestApp2.apk" />
<option name="test-file-name" value="SimpleServiceTestApp3.apk" />
+ <option name="test-file-name" value="FakeMediaApp.apk" />
</target_preparer>
<!-- Create place to store apks -->
diff --git a/services/tests/servicestests/apks/install_uses_sdk/Android.bp b/services/tests/servicestests/apks/install_uses_sdk/Android.bp
index a51293d..2894395 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/Android.bp
+++ b/services/tests/servicestests/apks/install_uses_sdk/Android.bp
@@ -32,9 +32,9 @@
}
android_test_helper_app {
- name: "FrameworksServicesTests_install_uses_sdk_r5",
+ name: "FrameworksServicesTests_install_uses_sdk_r1000",
defaults: ["FrameworksServicesTests_apks_defaults"],
- manifest: "AndroidManifest-r5.xml",
+ manifest: "AndroidManifest-r1000.xml",
}
android_test_helper_app {
@@ -44,9 +44,9 @@
}
android_test_helper_app {
- name: "FrameworksServicesTests_install_uses_sdk_r0_s5",
+ name: "FrameworksServicesTests_install_uses_sdk_r0_s1000",
defaults: ["FrameworksServicesTests_apks_defaults"],
- manifest: "AndroidManifest-r0-s5.xml",
+ manifest: "AndroidManifest-r0-s1000.xml",
}
android_test_helper_app {
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
similarity index 97%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
rename to services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
index bafe4c4..25743b8 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
+++ b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
@@ -19,7 +19,7 @@
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This fails because 31 is not version 5 -->
<extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
- <extension-sdk android:sdkVersion="31" android:minExtensionVersion="5" />
+ <extension-sdk android:sdkVersion="31" android:minExtensionVersion="1000" />
</uses-sdk>
<application>
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r1000.xml
similarity index 97%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
rename to services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r1000.xml
index 7723d05..9bf9254 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
+++ b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r1000.xml
@@ -18,7 +18,7 @@
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This will fail to install, because minExtensionVersion is not met -->
- <extension-sdk android:sdkVersion="30" android:minExtensionVersion="5" />
+ <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
</uses-sdk>
<application>
diff --git a/services/tests/servicestests/res/xml/usertypes_test_full.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
index 099ccbe..9568143 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_full.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -16,7 +16,8 @@
<user-types>
<full-type
name='android.test.1'
- max-allowed-per-parent='12' >
+ max-allowed-per-parent='12'
+ max-allowed='17' >
<default-restrictions no_remove_user='true' no_bluetooth='true' />
<badge-colors>
<item res='@*android:color/profile_badge_1' />
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index b27f49d..1a6dae37 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -33,6 +33,7 @@
<user-properties
showInLauncher='2020'
startWithParent='false'
+ useParentsContacts='false'
/>
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index 42c4129..653ed1a 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -36,8 +37,7 @@
import org.junit.runner.RunWith;
import java.io.FileDescriptor;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class BinaryTransparencyServiceTest {
@@ -96,7 +96,7 @@
@Test
public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException {
prepApexInfo();
- Map result = mTestInterface.getApexInfo();
+ List result = mTestInterface.getApexInfo();
Assert.assertNotNull("Apex info map should not be null", result);
Assert.assertFalse("Apex info map should not be empty", result.isEmpty());
}
@@ -105,13 +105,18 @@
public void getApexInfo_postInitialize_returnsActualApexs()
throws RemoteException, PackageManager.NameNotFoundException {
prepApexInfo();
- Map result = mTestInterface.getApexInfo();
+ List resultList = mTestInterface.getApexInfo();
PackageManager pm = mContext.getPackageManager();
Assert.assertNotNull(pm);
- HashMap<PackageInfo, String> castedResult = (HashMap<PackageInfo, String>) result;
- for (PackageInfo packageInfo : castedResult.keySet()) {
- Assert.assertTrue(packageInfo.packageName + "is not an APEX!", packageInfo.isApex);
+ List<Bundle> castedResult = (List<Bundle>) resultList;
+ for (Bundle resultBundle : castedResult) {
+ PackageInfo resultPackageInfo = resultBundle.getParcelable(
+ BinaryTransparencyService.BUNDLE_PACKAGE_INFO, PackageInfo.class);
+ Assert.assertNotNull("PackageInfo for APEX should not be null",
+ resultPackageInfo);
+ Assert.assertTrue(resultPackageInfo.packageName + "is not an APEX!",
+ resultPackageInfo.isApex);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 935d1d8..a49214f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -38,6 +38,9 @@
import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
+import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
@@ -99,6 +102,7 @@
import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -158,12 +162,18 @@
REPORT_USER_SWITCH_MSG,
USER_SWITCH_TIMEOUT_MSG,
USER_START_MSG,
+ USER_VISIBILITY_CHANGED_MSG,
USER_CURRENT_MSG);
- private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+ private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
+ private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+ USER_START_MSG,
+ USER_VISIBILITY_CHANGED_MSG,
+ REPORT_LOCKED_BOOT_COMPLETE_MSG);
+
@Before
public void setUp() throws Exception {
runWithDexmakerShareClassLoader(() -> {
@@ -182,6 +192,12 @@
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
+ // Starts with a generic assumption that the user starts visible, but on tests where
+ // that's not the case, the test should call mockAssignUserToMainDisplay()
+ doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE)
+ .when(mInjector.mUserManagerInternalMock)
+ .assignUserToDisplayOnStart(anyInt(), anyInt(), anyBoolean(), anyInt());
+
mUserController = new UserController(mInjector);
mUserController.setAllowUserUnlocking(true);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -209,16 +225,29 @@
@Test
public void testStartUser_background() {
+ mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ false);
verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
}
@Test
+ public void testStartUser_displayAssignmentFailed() {
+ doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
+ .when(mInjector.mUserManagerInternalMock)
+ .assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(), eq(true), anyInt());
+
+ boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
+
+ assertWithMessage("startUser(%s, foreground=true)", TEST_USER_ID).that(started).isFalse();
+ }
+
+ @Test
public void testStartUserOnSecondaryDisplay_defaultDisplay() {
assertThrows(IllegalArgumentException.class, () -> mUserController
.startUserOnSecondaryDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY));
@@ -238,7 +267,7 @@
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ true);
}
@Test
@@ -264,6 +293,8 @@
@Test
public void testStartPreCreatedUser_background() throws Exception {
+ mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
@@ -282,8 +313,6 @@
// binder calls, but their side effects (in this case, that the user is stopped right away)
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
.containsExactly(USER_START_MSG);
-
- verifyUserAssignedToDisplay(TEST_PRE_CREATED_USER_ID, Display.DEFAULT_DISPLAY);
}
private void startUserAssertions(
@@ -293,8 +322,10 @@
assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
}
- private void startBackgroundUserAssertions() {
- startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
+ private void startBackgroundUserAssertions(boolean visible) {
+ startUserAssertions(START_BACKGROUND_USER_ACTIONS,
+ visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
+ : START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
}
private void startForegroundUserAssertions() {
@@ -678,19 +709,24 @@
@Test
public void testStartProfile() throws Exception {
+ mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ true);
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
+ mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
mockIsUsersOnSecondaryDisplaysEnabled(true);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ true);
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@@ -947,24 +983,33 @@
when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
}
+ private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
+ @UserAssignmentResult int result) {
+ when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
+ /* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(result);
+ }
+
private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- verify(mInjector.getUserManagerInternal()).assignUserToDisplay(userId, displayId);
+ verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
+ anyBoolean(), eq(displayId));
}
private void verifyUserNeverAssignedToDisplay() {
- verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt());
+ verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplayOnStart(anyInt(),
+ anyInt(), anyBoolean(), anyInt());
}
private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
- verify(mInjector.getUserManagerInternal()).unassignUserFromDisplay(userId);
+ verify(mInjector.getUserManagerInternal()).unassignUserFromDisplayOnStop(userId);
}
private void verifyUserUnassignedFromDisplayNeverCalled(@UserIdInt int userId) {
- verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
+ verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
}
private void verifySystemUserVisibilityChangedNotified(boolean visible) {
- verify(mInjector).notifySystemUserVisibilityChanged(visible);
+ verify(mInjector).onUserVisibilityChanged(UserHandle.USER_SYSTEM, visible);
}
// Should be public to allow mocking
@@ -1104,13 +1149,13 @@
}
@Override
- void onUserStarting(@UserIdInt int userId, boolean visible) {
- Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
+ void onUserStarting(@UserIdInt int userId) {
+ Log.i(TAG, "onUserStarting(" + userId + ")");
}
@Override
- void notifySystemUserVisibilityChanged(boolean visible) {
- Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")");
+ void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ Log.i(TAG, "onUserVisibilityChanged(" + userId + ", " + visible + ")");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 0cff4f1..bb00634 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -125,12 +125,9 @@
mProbe.destroy();
mProbe.enable();
- AtomicInteger lux = new AtomicInteger(10);
- mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
-
verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
verifyNoMoreInteractions(mSensorManager);
- assertThat(lux.get()).isLessThan(0);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0);
}
@Test
@@ -323,15 +320,27 @@
}
@Test
- public void testNoNextLuxWhenDestroyed() {
+ public void testDestroyAllowsAwaitLuxExactlyOnce() {
+ final float lastValue = 5.5f;
mProbe.destroy();
- AtomicInteger lux = new AtomicInteger(-20);
+ AtomicInteger lux = new AtomicInteger(10);
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
- assertThat(lux.get()).isEqualTo(-1);
- verify(mSensorManager, never()).registerListener(
+ verify(mSensorManager).registerListener(
mSensorEventListenerCaptor.capture(), any(), anyInt());
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{lastValue}));
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
+ verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+
+ lux.set(22);
+ mProbe.enable();
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ mProbe.enable();
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
verifyNoMoreInteractions(mSensorManager);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 2afc4d7..1f29bec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors.face.aidl;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -25,7 +26,10 @@
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
import android.os.Handler;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -36,6 +40,7 @@
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -74,12 +79,18 @@
private BiometricContext mBiometricContext;
@Mock
private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private IFace mDaemon;
+ @Mock
+ private BiometricStateCallback mBiometricStateCallback;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
private Sensor.HalSessionCallback mHalCallback;
+ private FaceProvider mFaceProvider;
+ private SensorProps[] mSensorProps;
@Before
public void setUp() {
@@ -99,6 +110,16 @@
mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
TAG, mScheduler, SENSOR_ID,
USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
+
+ final SensorProps sensor1 = new SensorProps();
+ sensor1.commonProps = new CommonProps();
+ sensor1.commonProps.sensorId = 0;
+ final SensorProps sensor2 = new SensorProps();
+ sensor2.commonProps = new CommonProps();
+ sensor2.commonProps.sensorId = 1;
+ mSensorProps = new SensorProps[]{sensor1, sensor2};
+ mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
+ mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
}
@Test
@@ -128,6 +149,18 @@
verifyNotLocked();
}
+ @Test
+ public void onBinderDied_noErrorOnNullClient() {
+ mScheduler.reset();
+ assertNull(mScheduler.getCurrentClient());
+ mFaceProvider.binderDied();
+
+ for (int i = 0; i < mFaceProvider.mSensors.size(); i++) {
+ final Sensor sensor = mFaceProvider.mSensors.valueAt(i);
+ assertNull(sensor.getSessionForUser(USER_ID));
+ }
+ }
+
private void verifyNotLocked() {
assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 22d1bc5..22c53d3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,8 +16,6 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
@@ -87,7 +85,7 @@
private static final int USER_ID = 8;
private static final long OP_ID = 7;
private static final long REQUEST_ID = 88;
- private static final int POINTER_ID = 0;
+ private static final int POINTER_ID = 3;
private static final int TOUCH_X = 8;
private static final int TOUCH_Y = 20;
private static final float TOUCH_MAJOR = 4.4f;
@@ -135,8 +133,6 @@
@Captor
private ArgumentCaptor<OperationContext> mOperationContextCaptor;
@Captor
- private ArgumentCaptor<PointerContext> mPointerContextCaptor;
- @Captor
private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
private final TestLooper mLooper = new TestLooper();
@@ -176,7 +172,10 @@
public void pointerUp_v1() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
- client.onPointerUp();
+
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ client.onPointerUp(pc);
verify(mHal).onPointerUp(eq(POINTER_ID));
verify(mHal, never()).onPointerUpWithContext(any());
@@ -186,10 +185,17 @@
public void pointerDown_v1() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
- client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
- verify(mHal).onPointerDown(eq(0),
- eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ pc.x = TOUCH_X;
+ pc.y = TOUCH_Y;
+ pc.minor = TOUCH_MINOR;
+ pc.major = TOUCH_MAJOR;
+ client.onPointerDown(pc);
+
+ verify(mHal).onPointerDown(eq(POINTER_ID), eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MINOR),
+ eq(TOUCH_MAJOR));
verify(mHal, never()).onPointerDownWithContext(any());
}
@@ -197,26 +203,30 @@
public void pointerUpWithContext_v2() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(2);
client.start(mCallback);
- client.onPointerUp();
- verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ client.onPointerUp(pc);
+
+ verify(mHal).onPointerUpWithContext(eq(pc));
verify(mHal, never()).onPointerUp(eq(POINTER_ID));
-
- final PointerContext pContext = mPointerContextCaptor.getValue();
- assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
}
@Test
public void pointerDownWithContext_v2() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(2);
client.start(mCallback);
- client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
- verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ pc.x = TOUCH_X;
+ pc.y = TOUCH_Y;
+ pc.minor = TOUCH_MINOR;
+ pc.major = TOUCH_MAJOR;
+ client.onPointerDown(pc);
+
+ verify(mHal).onPointerDownWithContext(eq(pc));
verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
-
- final PointerContext pContext = mPointerContextCaptor.getValue();
- assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
}
@Test
@@ -373,6 +383,7 @@
@Test
public void fingerprintPowerIgnoresAuthInWindow() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
@@ -383,11 +394,13 @@
mLooper.dispatchAll();
verify(mCallback).onClientFinished(any(), eq(false));
+ verify(mCancellationSignal).cancel();
}
@Test
public void fingerprintAuthIgnoredWaitingForPower() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
@@ -398,11 +411,13 @@
mLooper.dispatchAll();
verify(mCallback).onClientFinished(any(), eq(false));
+ verify(mCancellationSignal).cancel();
}
@Test
- public void fingerprintAuthSucceedsAfterPowerWindow() throws Exception {
+ public void fingerprintAuthFailsWhenAuthAfterPower() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
@@ -416,7 +431,9 @@
mLooper.moveTimeForward(1000);
mLooper.dispatchAll();
- verify(mCallback).onClientFinished(any(), eq(true));
+ verify(mCallback, never()).onClientFinished(any(), eq(true));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 38b06c4..c3d4783 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -18,8 +18,6 @@
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -74,7 +72,7 @@
private static final byte[] HAT = new byte[69];
private static final int USER_ID = 8;
private static final long REQUEST_ID = 9;
- private static final int POINTER_ID = 0;
+ private static final int POINTER_ID = 3;
private static final int TOUCH_X = 8;
private static final int TOUCH_Y = 20;
private static final float TOUCH_MAJOR = 4.4f;
@@ -153,7 +151,10 @@
public void pointerUp_v1() throws RemoteException {
final FingerprintEnrollClient client = createClient(1);
client.start(mCallback);
- client.onPointerUp();
+
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ client.onPointerUp(pc);
verify(mHal).onPointerUp(eq(POINTER_ID));
verify(mHal, never()).onPointerUpWithContext(any());
@@ -163,10 +164,17 @@
public void pointerDown_v1() throws RemoteException {
final FingerprintEnrollClient client = createClient(1);
client.start(mCallback);
- client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
- verify(mHal).onPointerDown(eq(0),
- eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ pc.x = TOUCH_X;
+ pc.y = TOUCH_Y;
+ pc.minor = TOUCH_MINOR;
+ pc.major = TOUCH_MAJOR;
+ client.onPointerDown(pc);
+
+ verify(mHal).onPointerDown(eq(POINTER_ID), eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MINOR),
+ eq(TOUCH_MAJOR));
verify(mHal, never()).onPointerDownWithContext(any());
}
@@ -174,26 +182,30 @@
public void pointerUpWithContext_v2() throws RemoteException {
final FingerprintEnrollClient client = createClient(2);
client.start(mCallback);
- client.onPointerUp();
- verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
- verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ client.onPointerUp(pc);
- final PointerContext pContext = mPointerContextCaptor.getValue();
- assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ verify(mHal).onPointerUpWithContext(eq(pc));
+ verify(mHal, never()).onPointerUp(anyInt());
}
@Test
public void pointerDownWithContext_v2() throws RemoteException {
final FingerprintEnrollClient client = createClient(2);
client.start(mCallback);
- client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
- verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+ PointerContext pc = new PointerContext();
+ pc.pointerId = POINTER_ID;
+ pc.x = TOUCH_X;
+ pc.y = TOUCH_Y;
+ pc.minor = TOUCH_MINOR;
+ pc.major = TOUCH_MAJOR;
+ client.onPointerDown(pc);
+
+ verify(mHal).onPointerDownWithContext(eq(pc));
verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
-
- final PointerContext pContext = mPointerContextCaptor.getValue();
- assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
}
@Test
@@ -204,8 +216,10 @@
verify(mLuxProbe).enable();
client.onAcquired(2, 0);
- client.onPointerUp();
- client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ PointerContext pc = new PointerContext();
+ client.onPointerUp(pc);
+ client.onPointerDown(pc);
verify(mLuxProbe, never()).disable();
verify(mLuxProbe, never()).destroy();
diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
index ea746d1..faad961 100644
--- a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
+++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
@@ -30,7 +30,7 @@
import android.view.Display;
import android.view.Surface;
-import java.util.HashMap;
+import java.util.Map;
@RunWith(JUnit4.class)
public class CameraServiceProxyTest {
@@ -75,24 +75,22 @@
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(
CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
// Check rotation and lens facing combinations
- HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{
- put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
- put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
- put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
- put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
- }};
+ Map<Integer, Integer> backFacingMap = Map.of(
+ Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+ Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+ Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+ Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
taskInfo.isFixedOrientationPortrait = true;
backFacingMap.forEach((key, value) -> {
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
key, CameraCharacteristics.LENS_FACING_BACK,
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
});
- HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{
- put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
- put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
- put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
- put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
- }};
+ Map<Integer, Integer> frontFacingMap = Map.of(
+ Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+ Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+ Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+ Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
frontFacingMap.forEach((key, value) -> {
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
key, CameraCharacteristics.LENS_FACING_FRONT,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 5fda3d6..c715a21 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,6 +16,9 @@
package com.android.server.companion.virtual;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static com.google.common.truth.Truth.assertThat;
@@ -44,6 +47,7 @@
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -240,6 +244,55 @@
mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
mActivityListener, mRunningAppsChangedCallback, params);
+ mVdms.addVirtualDevice(mDeviceImpl);
+ }
+
+ @Test
+ public void getDevicePolicy_invalidDeviceId_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(
+ VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_defaultDeviceId_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(
+ VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_nonExistentDeviceId_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_unspecifiedPolicy_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_returnsCustom() {
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setBlockedActivities(getBlockedActivities())
+ .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .build();
+ mDeviceImpl = new VirtualDeviceImpl(mContext,
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
+ mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
+ mActivityListener, mRunningAppsChangedCallback, params);
+ mVdms.addVirtualDevice(mDeviceImpl);
+
+ assertThat(
+ mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_CUSTOM);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 77f1e24..036b6df 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -37,6 +37,8 @@
VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+ .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
+ VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
.build();
Parcel parcel = Parcel.obtain();
originalParams.writeToParcel(parcel, 0);
@@ -47,5 +49,7 @@
assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
+ assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
+ .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 4c939f0..0262f56 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -82,6 +82,7 @@
/* blockedActivities= */ new ArraySet<>(),
VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
/* activityListener= */ null,
+ /* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
/* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ddb3049..8e669f0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -140,7 +140,7 @@
import android.security.keystore.AttestationUtils;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
-import android.test.MoreAsserts; // TODO(b/171932723): replace by Truth
+import android.test.MoreAsserts;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -5087,7 +5087,7 @@
}
@Test
- public void testWipeDataDeviceOwner() throws Exception {
+ public void testWipeDevice_DeviceOwner() throws Exception {
setDeviceOwner();
when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
@@ -5096,7 +5096,7 @@
when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
thenReturn("Just a test string.");
- dpm.wipeData(0);
+ dpm.wipeDevice(0);
verifyRebootWipeUserData(/* wipeEuicc= */ false);
}
@@ -5111,13 +5111,13 @@
when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
thenReturn("Just a test string.");
- dpm.wipeData(WIPE_EUICC);
+ dpm.wipeDevice(WIPE_EUICC);
verifyRebootWipeUserData(/* wipeEuicc= */ true);
}
@Test
- public void testWipeDataDeviceOwnerDisallowed() throws Exception {
+ public void testWipeDevice_DeviceOwnerDisallowed() throws Exception {
setDeviceOwner();
when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
@@ -5128,7 +5128,7 @@
// The DO is not allowed to wipe the device if the user restriction was set
// by the system
assertExpectException(SecurityException.class, /* messageRegex= */ null,
- () -> dpm.wipeData(0));
+ () -> dpm.wipeDevice(0));
}
@Test
@@ -7986,7 +7986,7 @@
}
@Test
- public void testWipeData_financeDo_success() throws Exception {
+ public void testWipeDevice_financeDo_success() throws Exception {
setDeviceOwner();
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
when(getServices().userManager.getUserRestrictionSource(
@@ -7997,7 +7997,7 @@
.getString(R.string.work_profile_deleted_description_dpm_wipe))
.thenReturn("Test string");
- dpm.wipeData(0);
+ dpm.wipeDevice(0);
verifyRebootWipeUserData(/* wipeEuicc= */ false);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
index df672c9..2c4fe53 100644
--- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -424,7 +424,7 @@
@Override
public LocalDate getLocalDate() {
- return LocalDate.from(mLocalDate);
+ return mLocalDate;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 9e61cab..71f3d15 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -21,6 +21,7 @@
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
@@ -31,6 +32,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -48,6 +51,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
@@ -76,6 +80,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.test.FakeSettingsProvider;
@@ -2385,16 +2390,83 @@
@Test
public void testNotifyDefaultDisplayDeviceUpdated() {
- DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
- when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{});
- when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{});
- when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{});
- when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{});
+ Resources resources = mock(Resources.class);
+ when(mContext.getResources()).thenReturn(resources);
+ when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ .thenReturn(75);
+ when(resources.getInteger(R.integer.config_defaultRefreshRate))
+ .thenReturn(45);
+ when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{5});
+ when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{10});
+ when(
+ resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{250});
+ when(
+ resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{7000});
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ // We don't expect any interaction with DeviceConfig when the director is initialized
+ // because we explicitly avoid doing this as this can lead to a latency spike in the
+ // startup of DisplayManagerService
+ // Verify all the loaded values are from DisplayDeviceConfig
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
+ 0.0);
+ assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+ new int[]{250});
+ assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+ new int[]{7000});
+ assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+ new int[]{5});
+ assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+ new int[]{10});
+
+ // Notify that the default display is updated, such that DisplayDeviceConfig has new values
+ DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+ when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(50);
+ when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+ when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+ when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+ when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- verify(displayDeviceConfig).getDefaultRefreshRate();
- verify(displayDeviceConfig).getDefaultPeakRefreshRate();
+
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55,
+ 0.0);
+ assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+ new int[]{210});
+ assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+ new int[]{2100});
+ assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+ new int[]{25});
+ assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+ new int[]{30});
+
+ // Notify that the default display is updated, such that DeviceConfig has new values
+ FakeDeviceConfig config = mInjector.getDeviceConfig();
+ config.setDefaultPeakRefreshRate(60);
+ config.setLowAmbientBrightnessThresholds(new int[]{20});
+ config.setLowDisplayBrightnessThresholds(new int[]{10});
+ config.setHighDisplayBrightnessThresholds(new int[]{255});
+ config.setHighAmbientBrightnessThresholds(new int[]{8000});
+
+ director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
+ 0.0);
+ assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+ new int[]{255});
+ assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+ new int[]{8000});
+ assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+ new int[]{10});
+ assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+ new int[]{20});
}
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
@@ -2484,6 +2556,12 @@
String.valueOf(fps));
}
+ void setDefaultPeakRefreshRate(int fps) {
+ putPropertyAndNotify(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
+ String.valueOf(fps));
+ }
+
void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 0454587..a419b3f 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -51,6 +51,12 @@
}
}
+ public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+ Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+ setter.setAccessible(true);
+ setter.invoke(sensor, maximumRange, 1);
+ }
+
public static Sensor createSensor(int type, String strType) throws Exception {
Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
constr.setAccessible(true);
@@ -59,6 +65,16 @@
return sensor;
}
+ public static Sensor createSensor(int type, String strType, float maximumRange)
+ throws Exception {
+ Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+ constr.setAccessible(true);
+ Sensor sensor = constr.newInstance();
+ setSensorType(sensor, type, strType);
+ setMaximumRange(sensor, maximumRange);
+ return sensor;
+ }
+
/**
* Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
* display-address implementation in our code. Intentionally uses default object (reference)
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index fabf535..d332b30 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,8 +39,6 @@
getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
mBrightnessEvent.setPhysicalDisplayId("test");
mBrightnessEvent.setLux(100.0f);
- mBrightnessEvent.setFastAmbientLux(90.0f);
- mBrightnessEvent.setSlowAmbientLux(85.0f);
mBrightnessEvent.setPreThresholdLux(150.0f);
mBrightnessEvent.setTime(System.currentTimeMillis());
mBrightnessEvent.setInitialBrightness(25.0f);
@@ -50,6 +48,7 @@
mBrightnessEvent.setRbcStrength(-1);
mBrightnessEvent.setThermalMax(0.65f);
mBrightnessEvent.setPowerFactor(0.2f);
+ mBrightnessEvent.setWasShortTermModelActive(true);
mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
mBrightnessEvent.setFlags(0);
mBrightnessEvent.setAdjustmentFlags(0);
@@ -69,9 +68,9 @@
String actualString = mBrightnessEvent.toString(false);
String expectedString =
"BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
- + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
- + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
- + " [ low_pwr ], autoBrightness=true";
+ + " preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off, rbcStrength=-1,"
+ + " thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true, flags=,"
+ + " reason=doze [ low_pwr ], autoBrightness=true";
assertEquals(expectedString, actualString);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
new file mode 100644
index 0000000..cbeaf7b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.server.display.brightness;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+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)
+public final class DisplayBrightnessControllerTest {
+ private static final int DISPLAY_ID = 1;
+
+ @Mock
+ private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+ @Mock
+ private Context mContext;
+
+ private DisplayBrightnessController mDisplayBrightnessController;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ DisplayBrightnessController.Injector injector = new DisplayBrightnessController.Injector() {
+ @Override
+ DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
+ Context context, int displayId) {
+ return mDisplayBrightnessStrategySelector;
+ }
+ };
+ mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector,
+ DISPLAY_ID);
+ }
+
+ @Test
+ public void updateBrightnessWorksAsExpected() {
+ DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
+ DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
+ int targetDisplayState = Display.STATE_DOZE;
+ when(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ targetDisplayState)).thenReturn(displayBrightnessStrategy);
+ mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
+ verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
+ }
+
+ @Test
+ public void isAllowAutoBrightnessWhileDozingConfigDelegatesToDozeBrightnessStrategy() {
+ mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
+ verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
new file mode 100644
index 0000000..ba31e8c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.server.display.brightness;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+
+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)
+public final class DisplayBrightnessStrategySelectorTest {
+ private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false;
+ private static final int DISPLAY_ID = 1;
+
+ @Mock
+ private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
+ @Mock
+ private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
+ @Mock
+ private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+
+ private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ DisplayBrightnessStrategySelector.Injector injector =
+ new DisplayBrightnessStrategySelector.Injector() {
+ @Override
+ ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
+ return mScreenOffBrightnessModeStrategy;
+ }
+
+ @Override
+ DozeBrightnessStrategy getDozeBrightnessStrategy() {
+ return mDozeBrightnessModeStrategy;
+ }
+
+ @Override
+ InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
+ return mInvalidBrightnessStrategy;
+ }
+ };
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ injector, DISPLAY_ID);
+
+ }
+
+ @Test
+ public void selectStrategySelectsDozeStrategyWhenValid() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
+ DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ Display.STATE_DOZE), mDozeBrightnessModeStrategy);
+ }
+
+ @Test
+ public void selectStrategySelectsScreenOffStrategyWhenValid() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ Display.STATE_OFF), mScreenOffBrightnessModeStrategy);
+ }
+
+ @Test
+ public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ Display.STATE_ON), mInvalidBrightnessStrategy);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
new file mode 100644
index 0000000..29652ff
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DozeBrightnessStrategyTest {
+ private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
+
+ @Before
+ public void before() {
+ mDozeBrightnessModeStrategy = new DozeBrightnessStrategy();
+ }
+
+ @Test
+ public void updateBrightnessWorksAsExpectedWhenScreenDozeStateIsRequested() {
+ DisplayPowerRequest displayPowerRequest = new DisplayPowerRequest();
+ float dozeScreenBrightness = 0.2f;
+ displayPowerRequest.dozeScreenBrightness = dozeScreenBrightness;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_DOZE);
+ DisplayBrightnessState expectedDisplayBrightnessState =
+ new DisplayBrightnessState.Builder()
+ .setBrightness(dozeScreenBrightness)
+ .setBrightnessReason(brightnessReason)
+ .setSdrBrightness(dozeScreenBrightness)
+ .build();
+ DisplayBrightnessState updatedDisplayBrightnessState =
+ mDozeBrightnessModeStrategy.updateBrightness(displayPowerRequest);
+ assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
new file mode 100644
index 0000000..0505475
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ScreenOffBrightnessStrategyTest {
+
+ private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
+
+ @Before
+ public void before() {
+ mScreenOffBrightnessModeStrategy = new ScreenOffBrightnessStrategy();
+ }
+
+ @Test
+ public void updateBrightnessWorksAsExpectedWhenScreenOffDisplayState() {
+ DisplayPowerRequest displayPowerRequest = new DisplayPowerRequest();
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF);
+ DisplayBrightnessState expectedDisplayBrightnessState =
+ new DisplayBrightnessState.Builder()
+ .setBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT)
+ .setSdrBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT)
+ .setBrightnessReason(brightnessReason)
+ .build();
+ DisplayBrightnessState updatedDisplayBrightnessState =
+ mScreenOffBrightnessModeStrategy.updateBrightness(displayPowerRequest);
+ assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index f9b8373..9672085 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -108,7 +108,7 @@
}
@Override
- public boolean hasFsverity(String path) {
+ public boolean isFromTrustedProvider(String path, byte[] signature) {
return mHasFsverityPaths.contains(path);
}
@@ -291,6 +291,32 @@
}
@Test
+ public void construct_missingSignatureFile() throws Exception {
+ UpdatableFontDir dirForPreparation = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
+ mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
+ dirForPreparation.loadFontFileMap();
+ dirForPreparation.update(Arrays.asList(
+ newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE)));
+ assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
+
+ // Remove signature file next to the font file.
+ File fontDir = dirForPreparation.getPostScriptMap().get("foo");
+ File sigFile = new File(fontDir.getParentFile(), "font.fsv_sig");
+ assertThat(sigFile.exists()).isTrue();
+ sigFile.delete();
+
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
+ mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
+ dir.loadFontFileMap();
+ // The font file should be removed and should not be loaded.
+ assertThat(dir.getPostScriptMap()).isEmpty();
+ assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+ assertThat(dir.getFontFamilyMap()).isEmpty();
+ }
+
+ @Test
public void construct_olderThanPreinstalledFont() throws Exception {
Function<Map<String, File>, FontConfig> configSupplier = (map) -> {
FontConfig.Font fooFont = new FontConfig.Font(
@@ -782,8 +808,8 @@
UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() {
@Override
- public boolean hasFsverity(String path) {
- return mFakeFsverityUtil.hasFsverity(path);
+ public boolean isFromTrustedProvider(String path, byte[] signature) {
+ return mFakeFsverityUtil.isFromTrustedProvider(path, signature);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 54baf18..82c3401 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -849,4 +849,53 @@
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
anyInt());
}
+
+ @Test
+ public void tvSendRequestArcTerminationOnSleep() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+
+ mNativeWrapper.onCecMessage(initiateArc);
+ mTestLooper.dispatchAll();
+
+ // Finish querying SADs
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // ARC should be established after RequestSadAction is finished
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 9092ec3..0884b78 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -367,6 +367,7 @@
assertFalse(item_en_us_allcaps.mIsSystemLocale);
}
+ @SuppressWarnings("SelfComparison")
@Test
public void testImeSubtypeListComparator() throws Exception {
final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index 9c8e72c..f5029ec 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -71,7 +71,7 @@
private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
private static final long POLL_INTERVAL = 500;
- private static final long DEFAULT_WAIT_TIMEOUT = 5000;
+ private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
private Context mContext;
private AppOpsManager mAppOpsManager;
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 f138311..dc47b5e 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,7 +8,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -20,8 +19,6 @@
import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.test.RenamingDelegatingContext;
@@ -32,7 +29,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.HexDump;
import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobSet;
import com.android.server.job.controllers.JobStatus;
@@ -44,7 +40,6 @@
import java.time.Clock;
import java.time.ZoneOffset;
-import java.util.Arrays;
import java.util.Iterator;
/**
@@ -143,19 +138,23 @@
assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0);
- assertTasksEqual(task, loadedTaskStatus.getJob());
+ assertJobsEqual(ts, loadedTaskStatus);
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
- assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
- assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION,
- loadedTaskStatus.getInternalFlags());
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
}
@Test
- public void testWritingTwoFilesToDisk() throws Exception {
+ public void testWritingTwoJobsToDisk_singleFile() throws Exception {
+ mTaskStoreUnderTest.setUseSplitFiles(false);
+ runWritingTwoJobsToDisk();
+ }
+
+ @Test
+ public void testWritingTwoJobsToDisk_splitFiles() throws Exception {
+ mTaskStoreUnderTest.setUseSplitFiles(true);
+ runWritingTwoJobsToDisk();
+ }
+
+ private void runWritingTwoJobsToDisk() throws Exception {
final JobInfo task1 = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
@@ -169,8 +168,10 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setPersisted(true)
.build();
- final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, SOME_UID, null, -1, null);
- final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1, null);
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+ final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
mTaskStoreUnderTest.add(taskStatus1);
mTaskStoreUnderTest.add(taskStatus2);
waitForPendingIo();
@@ -189,19 +190,10 @@
loaded2 = tmp;
}
- assertTasksEqual(task1, loaded1.getJob());
- assertTasksEqual(task2, loaded2.getJob());
+ assertJobsEqual(taskStatus1, loaded1);
+ assertJobsEqual(taskStatus2, loaded2);
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
- // Check that the loaded task has the correct runtimes.
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
}
@Test
@@ -227,7 +219,7 @@
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
- assertTasksEqual(task, loaded.getJob());
+ assertJobsEqual(taskStatus, loaded);
}
@Test
@@ -414,6 +406,35 @@
}
@Test
+ public void testEstimatedNetworkBytes() throws Exception {
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetwork(new NetworkRequest.Builder().build())
+ .setEstimatedNetworkBytes(
+ JobInfo.NETWORK_BYTES_UNKNOWN, JobInfo.NETWORK_BYTES_UNKNOWN)
+ .build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetwork(new NetworkRequest.Builder().build())
+ .setEstimatedNetworkBytes(5, 15)
+ .build());
+ }
+
+ @Test
+ public void testMinimumNetworkChunkBytes() throws Exception {
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetwork(new NetworkRequest.Builder().build())
+ .setMinimumNetworkChunkBytes(JobInfo.NETWORK_BYTES_UNKNOWN)
+ .build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetwork(new NetworkRequest.Builder().build())
+ .setMinimumNetworkChunkBytes(42)
+ .build());
+ }
+
+ @Test
public void testPersistedIdleConstraint() throws Exception {
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
@@ -502,62 +523,30 @@
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
- assertTasksEqual(first.getJob(), second.getJob());
+ assertJobsEqual(first, second);
}
/**
- * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
+ * Helper function to throw an error if the provided JobStatus objects are not equal.
*/
- private void assertTasksEqual(JobInfo first, JobInfo second) {
- assertEquals("Different task ids.", first.getId(), second.getId());
- assertEquals("Different components.", first.getService(), second.getService());
- assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
- assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
- assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
- second.getInitialBackoffMillis());
- assertEquals("Different backoff policy.", first.getBackoffPolicy(),
- second.getBackoffPolicy());
+ private void assertJobsEqual(JobStatus expected, JobStatus actual) {
+ assertEquals(expected.getJob(), actual.getJob());
- assertEquals("Invalid charging constraint.", first.isRequireCharging(),
- second.isRequireCharging());
- assertEquals("Invalid battery not low constraint.", first.isRequireBatteryNotLow(),
- second.isRequireBatteryNotLow());
- assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
- second.isRequireDeviceIdle());
- assertEquals("Invalid network type.",
- first.getNetworkType(), second.getNetworkType());
- assertEquals("Invalid network.",
- first.getRequiredNetwork(), second.getRequiredNetwork());
- assertEquals("Invalid deadline constraint.",
- first.hasLateConstraint(),
- second.hasLateConstraint());
- assertEquals("Invalid delay constraint.",
- first.hasEarlyConstraint(),
- second.hasEarlyConstraint());
- assertEquals("Extras don't match",
- first.getExtras().toString(), second.getExtras().toString());
- assertEquals("Transient xtras don't match",
- first.getTransientExtras().toString(), second.getTransientExtras().toString());
+ // Source UID isn't persisted, but the rest of the app info is.
+ assertEquals("Source package not equal",
+ expected.getSourcePackageName(), actual.getSourcePackageName());
+ assertEquals("Source user not equal", expected.getSourceUserId(), actual.getSourceUserId());
+ assertEquals("Calling UID not equal", expected.getUid(), actual.getUid());
+ assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId());
- // Since people can forget to add tests here for new fields, do one last
- // validity check based on bits-on-wire equality.
- final byte[] firstBytes = marshall(first);
- final byte[] secondBytes = marshall(second);
- if (!Arrays.equals(firstBytes, secondBytes)) {
- Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes));
- Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes));
- fail("Raw JobInfo aren't equal; see logs for details");
- }
- }
+ assertEquals("Internal flags not equal",
+ expected.getInternalFlags(), actual.getInternalFlags());
- private static byte[] marshall(Parcelable p) {
- final Parcel parcel = Parcel.obtain();
- try {
- p.writeToParcel(parcel, 0);
- return parcel.marshall();
- } finally {
- parcel.recycle();
- }
+ // Check that the loaded task has the correct runtimes.
+ compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+ expected.getEarliestRunTime(), actual.getEarliestRunTime());
+ compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+ expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
}
/**
@@ -572,5 +561,4 @@
}
private static class StubClass {}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
index 1e855a9..1eb4fa5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
@@ -58,4 +58,4 @@
} catch (Exception e) {
}
}
-};
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
new file mode 100644
index 0000000..1c4ee69
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.media;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaButtonReceiverHolderTest {
+
+ @Test
+ public void createMediaButtonReceiverHolder_resolvesNullComponentName() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ PendingIntent pi = PendingIntent.getBroadcast(context, /* requestCode= */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ MediaButtonReceiverHolder a = MediaButtonReceiverHolder.create(/* userId= */ 0, pi,
+ context.getPackageName());
+ Truth.assertWithMessage("Component name must match PendingIntent creator package.").that(
+ a.getComponentName()).isNull();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/OWNERS b/services/tests/servicestests/src/com/android/server/media/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8d2cffa..1f952c4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -60,10 +60,14 @@
.setShowInLauncher(21)
.setStartWithParent(false)
.setShowInSettings(45)
+ .setInheritDevicePolicy(67)
+ .setUseParentsContacts(false)
.build();
final UserProperties actualProps = new UserProperties(defaultProps);
actualProps.setShowInLauncher(14);
actualProps.setShowInSettings(32);
+ actualProps.setInheritDevicePolicy(51);
+ actualProps.setUseParentsContacts(true);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -102,11 +106,13 @@
.setShowInLauncher(2145)
.setStartWithParent(true)
.setShowInSettings(3452)
+ .setInheritDevicePolicy(1732)
.build();
final UserProperties orig = new UserProperties(defaultProps);
orig.setShowInLauncher(2841);
orig.setStartWithParent(false);
orig.setShowInSettings(1437);
+ orig.setInheritDevicePolicy(9456);
// Test every permission level. (Currently, it's linear so it's easy.)
for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -142,15 +148,20 @@
// Items requiring exposeAll.
assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
+ assertEqualGetterOrThrows(orig::getInheritDevicePolicy,
+ copy::getInheritDevicePolicy, exposeAll);
// Items requiring hasManagePermission - put them here using hasManagePermission.
assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
hasManagePermission);
+ assertEqualGetterOrThrows(orig::getUseParentsContacts,
+ copy::getUseParentsContacts, hasManagePermission);
// Items requiring hasQueryPermission - put them here using hasQueryPermission.
// Items with no permission requirements.
assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+
}
/**
@@ -190,5 +201,7 @@
assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
+ assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy());
+ assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 5f48004..d7c1e37 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -83,7 +83,8 @@
/* flags= */0,
/* letsPersonalDataIntoProfile= */false).build());
final UserProperties.Builder userProps = new UserProperties.Builder()
- .setShowInLauncher(17);
+ .setShowInLauncher(17)
+ .setUseParentsContacts(true);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
.setEnabled(1)
@@ -140,6 +141,7 @@
}
assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertTrue(type.getDefaultUserPropertiesReference().getUseParentsContacts());
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
@@ -182,6 +184,7 @@
final UserProperties props = type.getDefaultUserPropertiesReference();
assertNotNull(props);
assertFalse(props.getStartWithParent());
+ assertFalse(props.getUseParentsContacts());
assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
assertFalse(type.hasBadge());
@@ -263,7 +266,8 @@
final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
final UserProperties.Builder props = new UserProperties.Builder()
.setShowInLauncher(19)
- .setStartWithParent(true);
+ .setStartWithParent(true)
+ .setUseParentsContacts(true);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
@@ -289,7 +293,9 @@
assertEquals(Resources.ID_NULL, aospType.getIconBadge());
assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
- assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference()
+ .getUseParentsContacts());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -319,7 +325,9 @@
makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
aospType.getDefaultRestrictions()));
assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
- assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference()
+ .getUseParentsContacts());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
@@ -347,6 +355,7 @@
UserTypeDetails details = builders.get(userTypeFull).createUserTypeDetails();
assertEquals(UNLIMITED_NUMBER_OF_USERS, details.getMaxAllowedPerParent());
assertFalse(details.isEnabled());
+ assertEquals(17, details.getMaxAllowed());
assertTrue(UserRestrictionsUtils.areEqual(
makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
details.getDefaultRestrictions()));
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 df2c5dd..2e7e583 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
@@ -164,6 +165,14 @@
@Test
public void testCloneUser() throws Exception {
+
+ // Get the default properties for clone user type.
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_CLONE);
+ assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_CLONE)
+ .that(userTypeDetails).isNotNull();
+ final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
// Test that only one clone user can be created
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo = createProfileForUser("Clone user1",
@@ -187,6 +196,16 @@
.collect(Collectors.toList());
assertThat(cloneUsers.size()).isEqualTo(1);
+ // Check that the new clone user has the expected properties (relative to the defaults)
+ // provided that the test caller has the necessary permissions.
+ UserProperties cloneUserProperties =
+ mUserManager.getUserProperties(UserHandle.of(userInfo.id));
+ assertThat(typeProps.getUseParentsContacts())
+ .isEqualTo(cloneUserProperties.getUseParentsContacts());
+ assertThat(typeProps.getShowInLauncher())
+ .isEqualTo(cloneUserProperties.getShowInLauncher());
+ assertThrows(SecurityException.class, cloneUserProperties::getStartWithParent);
+
// Verify clone user parent
assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
@@ -600,7 +619,9 @@
// provided that the test caller has the necessary permissions.
assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
+ assertFalse(userProps.getUseParentsContacts());
assertThrows(SecurityException.class, userProps::getStartWithParent);
+ assertThrows(SecurityException.class, userProps::getInheritDevicePolicy);
}
// Make sure only max managed profiles can be created
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 699601b..8e6c014 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -575,10 +575,11 @@
assertEquals(0, minExtVers.get(31, -1));
Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
- appToError.put(Pair.create("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5),
+ appToError.put(Pair.create("install_uses_sdk.apk_r1000", R.raw.install_uses_sdk_r1000),
PackageManager.INSTALL_FAILED_OLDER_SDK);
- appToError.put(Pair.create("install_uses_sdk.apk_r0_s5", R.raw.install_uses_sdk_r0_s5),
- PackageManager.INSTALL_FAILED_OLDER_SDK);
+ appToError.put(
+ Pair.create("install_uses_sdk.apk_r0_s1000", R.raw.install_uses_sdk_r0_s1000),
+ PackageManager.INSTALL_FAILED_OLDER_SDK);
appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
@@ -595,7 +596,7 @@
int result = entry.getValue();
try {
parsePackage(filename, resId, x -> x);
- expect.withMessage("Expected parsing error %d from %s", result, filename).fail();
+ expect.withMessage("Expected parsing error %s from %s", result, filename).fail();
} catch (PackageManagerException expected) {
expect.that(expected.error).isEqualTo(result);
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 04ba7d3..136e79e 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -65,6 +65,7 @@
private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
DeviceState[].class);
private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
+ private static final int MAX_HINGE_ANGLE_EXCLUSIVE = 360;
private Context mContext;
private SensorManager mSensorManager;
@@ -268,11 +269,7 @@
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
- @Test
- public void create_sensor() throws Exception {
- Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
- when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
-
+ private DeviceStateProviderImpl create_sensorBasedProvider(Sensor sensor) {
String configString = "<device-state-config>\n"
+ " <device-state>\n"
+ " <identifier>1</identifier>\n"
@@ -310,14 +307,22 @@
+ " <name>" + sensor.getName() + "</name>\n"
+ " <value>\n"
+ " <min-inclusive>180</min-inclusive>\n"
+ + " <max>" + MAX_HINGE_ANGLE_EXCLUSIVE + "</max>\n"
+ " </value>\n"
+ " </sensor>\n"
+ " </conditions>\n"
+ " </device-state>\n"
+ "</device-state-config>\n";
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
- DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ return DeviceStateProviderImpl.createFromConfig(mContext,
config);
+ }
+
+ @Test
+ public void create_sensor() throws Exception {
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
@@ -371,6 +376,40 @@
}
@Test
+ public void test_invalidSensorValues() throws Exception {
+ // onStateChanged() should not be triggered by invalid sensor values.
+
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+ Mockito.clearInvocations(listener);
+
+ // First, switch to a non-default state.
+ SensorEvent event1 = mock(SensorEvent.class);
+ event1.sensor = sensor;
+ FieldSetter.setField(event1, event1.getClass().getField("values"), new float[]{90});
+ provider.onSensorChanged(event1);
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+ Mockito.clearInvocations(listener);
+
+ // Then, send an invalid sensor event, verify that onStateChanged() is not triggered.
+ SensorEvent event2 = mock(SensorEvent.class);
+ event2.sensor = sensor;
+ FieldSetter.setField(event2, event2.getClass().getField("values"),
+ new float[]{MAX_HINGE_ANGLE_EXCLUSIVE});
+
+ provider.onSensorChanged(event2);
+
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
+ }
+
+ @Test
public void create_invalidSensor() throws Exception {
Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of());
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index db2630e2..c81db92 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -389,6 +389,18 @@
mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
}
+ private void setBatteryLevel(int batteryLevel) {
+ when(mBatteryManagerInternalMock.getBatteryLevel())
+ .thenReturn(batteryLevel);
+ mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+ }
+
+ private void setBatteryHealth(int batteryHealth) {
+ when(mBatteryManagerInternalMock.getBatteryHealth())
+ .thenReturn(batteryHealth);
+ mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+ }
+
private void setAttentiveTimeout(int attentiveTimeoutMillis) {
Settings.Secure.putInt(
mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
@@ -413,6 +425,12 @@
.thenReturn(disable);
}
+ private void setDreamsBatteryLevelDrainConfig(int threshold) {
+ when(mResourcesSpy.getInteger(
+ com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
+ threshold);
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
@@ -658,17 +676,52 @@
}
/**
- * Tests that dreaming continues when undocking and configured to do so.
+ * Tests that dreaming stops when undocking and not configured to keep dreaming.
*/
@Test
- public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() {
+ public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() {
+ // Make sure "unplug turns on screen" is configured to true.
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+ .thenReturn(true);
+
createService();
startSystem();
- when(mResourcesSpy.getBoolean(
- com.android.internal.R.bool.config_keepDreamingWhenUndocking))
+ ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+ ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+ verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+ dreamManagerStateListener.capture());
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+
+ when(mBatteryManagerInternalMock.getPlugType())
+ .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+ setPluggedIn(false);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
+ /**
+ * Tests that dreaming continues when undocking and configured to do so.
+ */
+ @Test
+ public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() {
+ // Make sure "unplug turns on screen" is configured to true.
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
.thenReturn(true);
- mService.readConfigurationLocked();
+
+ createService();
+ startSystem();
+
+ ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+ ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+ verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+ dreamManagerStateListener.capture());
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
when(mBatteryManagerInternalMock.getPlugType())
.thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -682,6 +735,37 @@
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
}
+ /**
+ * Tests that dreaming stops when undocking while showing a dream that prevents it.
+ */
+ @Test
+ public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() {
+ // Make sure "unplug turns on screen" is configured to true.
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+ .thenReturn(true);
+
+ createService();
+ startSystem();
+
+ ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+ ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+ verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+ dreamManagerStateListener.capture());
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
+
+ when(mBatteryManagerInternalMock.getPlugType())
+ .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+ when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+ setPluggedIn(false);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
@Test
public void testWakefulnessDoze_goToSleep() {
createService();
@@ -921,6 +1005,41 @@
}
@Test
+ public void testBatteryDrainDuringDream() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setMinimumScreenOffTimeoutConfig(100);
+ setDreamsBatteryLevelDrainConfig(5);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setBatteryLevel(100);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ setBatteryLevel(90);
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+
+ // If battery overheat protection is enabled, we shouldn't count battery drain
+ setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ setBatteryLevel(70);
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+ }
+
+ @Test
public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
final String suspendBlockerName = "PowerManagerService.Display";
final String tag = "acq_causes_wakeup";
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 397770b..dcbdcdc 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -42,6 +42,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.IHintSession;
+import android.os.PerformanceHintManager;
import android.os.Process;
import com.android.server.FgThread;
@@ -250,6 +251,32 @@
}
@Test
+ public void testSendHint() throws Exception {
+ HintManagerService service = createService();
+ IBinder token = new Binder();
+
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+ a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
+ verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(),
+ eq(PerformanceHintManager.Session.CPU_LOAD_RESET));
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.sendHint(-1);
+ });
+
+ reset(mNativeWrapperMock);
+ // Set session to background, then the duration would not be updated.
+ service.mUidObserver.onUidStateChanged(
+ a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ FgThread.getHandler().runWithScissors(() -> { }, 500);
+ assertFalse(a.updateHintAllowed());
+ a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
+ verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt());
+ }
+
+ @Test
public void testDoHintInBackground() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 83139b0..5a482fc 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -44,6 +44,7 @@
import android.content.Intent;
import android.content.om.IOverlayManager;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
@@ -669,7 +670,10 @@
}
@Test
- public void testSetNavBarMode_setsModeKids() throws RemoteException {
+ public void testSetNavBarMode_setsModeKids() throws Exception {
+ mContext.setMockPackageManager(mPackageManager);
+ when(mPackageManager.getPackageInfo(anyString(),
+ any(PackageManager.PackageInfoFlags.class))).thenReturn(new PackageInfo());
int navBarModeKids = StatusBarManager.NAV_BAR_MODE_KIDS;
mStatusBarManagerService.setNavBarMode(navBarModeKids);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index fed8b40..bcdc65c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -21,12 +21,14 @@
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
@@ -34,14 +36,17 @@
new FakeServiceConfigAccessor();
private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
private TimeZoneState mTimeZoneState;
+ private TimeZoneDetectorStatus mStatus;
public FakeTimeZoneDetectorStrategy() {
mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
this::notifyChangeListeners);
}
- public void initializeConfiguration(ConfigurationInternal configuration) {
+ public void initializeConfigurationAndStatus(
+ ConfigurationInternal configuration, TimeZoneDetectorStatus status) {
mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+ mStatus = Objects.requireNonNull(status);
}
@Override
@@ -57,6 +62,7 @@
assertEquals("Multi-user testing not supported",
configurationInternal.getUserId(), userId);
return new TimeZoneCapabilitiesAndConfig(
+ mStatus,
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@@ -90,7 +96,7 @@
}
@Override
- public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+ public void handleLocationAlgorithmEvent(LocationAlgorithmEvent locationAlgorithmEvent) {
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
index 0f667b3..602842a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
@@ -16,13 +16,8 @@
package com.android.server.timezonedetector;
-import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-
-import android.os.ShellCommand;
import org.junit.Test;
@@ -49,11 +44,6 @@
assertEquals(certain1v1, certain1v2);
assertEquals(certain1v2, certain1v1);
- // DebugInfo must not be considered in equals().
- certain1v1.addDebugInfo("Debug info 1");
- certain1v2.addDebugInfo("Debug info 2");
- assertEquals(certain1v1, certain1v2);
-
long time2 = 2222L;
GeolocationTimeZoneSuggestion certain2 =
GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1);
@@ -71,40 +61,4 @@
assertNotEquals(certain1v1, certain3);
assertNotEquals(certain3, certain1v1);
}
-
- @Test(expected = IllegalArgumentException.class)
- public void testParseCommandLineArg_noZoneIdsArg() {
- ShellCommand testShellCommand =
- createShellCommandWithArgsAndOptions(Collections.emptyList());
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsUncertain() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
- "--zone_ids UNCERTAIN");
- assertNull(GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand)
- .getZoneIds());
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsEmpty() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--zone_ids EMPTY");
- assertEquals(Collections.emptyList(),
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsPresent() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
- "--zone_ids Europe/London,Europe/Paris");
- assertEquals(Arrays.asList("Europe/London", "Europe/Paris"),
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testParseCommandLineArg_unknownArgument() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--bad_arg 0");
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
new file mode 100644
index 0000000..4c14014
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 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.server.timezonedetector;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class LocationAlgorithmEventTest {
+
+ public static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ public static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+
+ @Test
+ public void testEquals() {
+ GeolocationTimeZoneSuggestion suggestion1 =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ LocationTimeZoneAlgorithmStatus status1 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event1v1 = new LocationAlgorithmEvent(status1, suggestion1);
+ assertEqualsAndHashCode(event1v1, event1v1);
+
+ LocationAlgorithmEvent event1v2 = new LocationAlgorithmEvent(status1, suggestion1);
+ assertEqualsAndHashCode(event1v1, event1v2);
+
+ GeolocationTimeZoneSuggestion suggestion2 =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(2222L);
+ LocationAlgorithmEvent event2 = new LocationAlgorithmEvent(status1, suggestion2);
+ assertNotEquals(event1v1, event2);
+
+ LocationTimeZoneAlgorithmStatus status2 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_READY, null);
+ LocationAlgorithmEvent event3 = new LocationAlgorithmEvent(status2, suggestion1);
+ assertNotEquals(event1v1, event3);
+
+ // DebugInfo must not be considered in equals().
+ event1v1.addDebugInfo("Debug info 1");
+ event1v2.addDebugInfo("Debug info 2");
+ assertEquals(event1v1, event1v2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noStatus() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ ShellCommand testShellCommand =
+ createShellCommandWithArgsAndOptions(
+ Arrays.asList("--suggestion", suggestion.toString()));
+
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_noSuggestion() {
+ GeolocationTimeZoneSuggestion suggestion = null;
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString()));
+
+ assertEquals(event, LocationAlgorithmEvent.parseCommandLineArg(testShellCommand));
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionUncertain() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "UNCERTAIN"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionEmpty() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Collections.emptyList());
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "EMPTY"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionPresent() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "Europe/London,Europe/Paris"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "Europe/London,Europe/Paris", "--bad_arg"));
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ }
+
+ private static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(two, one);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 223c532..ea801e8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
import static org.junit.Assert.assertEquals;
@@ -23,6 +27,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.UserIdInt;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -31,6 +36,7 @@
import org.junit.Test;
import java.util.Arrays;
+import java.util.List;
import java.util.function.Function;
/** Tests for {@link MetricsTimeZoneDetectorState}. */
@@ -38,6 +44,9 @@
private static final @UserIdInt int ARBITRARY_USER_ID = 1;
private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
@@ -50,11 +59,14 @@
.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
.build();
- private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+ public static final GeolocationTimeZoneSuggestion GEOLOCATION_SUGGESTION_CERTAIN =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
ARBITRARY_ELAPSED_REALTIME_MILLIS,
Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
+ private static final LocationAlgorithmEvent LOCATION_ALGORITHM_EVENT =
+ new LocationAlgorithmEvent(ARBITRARY_CERTAIN_STATUS, GEOLOCATION_SUGGESTION_CERTAIN);
+
private final OrdinalGenerator<String> mOrdinalGenerator =
new OrdinalGenerator<>(Function.identity());
@@ -68,7 +80,7 @@
MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
- TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+ TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
// Assert the content.
assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
@@ -88,9 +100,10 @@
assertEquals(expectedTelephonySuggestion,
metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+ List<String> expectedZoneIds = LOCATION_ALGORITHM_EVENT.getSuggestion().getZoneIds();
MetricsTimeZoneSuggestion expectedGeoSuggestion =
MetricsTimeZoneSuggestion.createCertain(
- GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+ expectedZoneIds.toArray(new String[0]),
new int[] { 3, 4 });
assertEquals(expectedGeoSuggestion,
metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
@@ -106,7 +119,7 @@
MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
- TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+ TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
// Assert the content.
assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 8909832..a02c8ca 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -16,14 +16,22 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.content.Context;
import android.os.HandlerThread;
@@ -41,6 +49,15 @@
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorInternalImplTest {
+ private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_STATUS,
+ ARBITRARY_LOCATION_CERTAIN_STATUS);
+
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
private static final String ARBITRARY_ZONE_ID = "TestZoneId";
private static final List<String> ARBITRARY_ZONE_IDS = Arrays.asList(ARBITRARY_ZONE_ID);
@@ -81,7 +98,8 @@
public void testGetCapabilitiesAndConfigForDpm() throws Exception {
final boolean autoDetectionEnabled = true;
ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
+ TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(testConfig, testStatus);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
@@ -93,6 +111,7 @@
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
new TimeZoneCapabilitiesAndConfig(
+ testStatus,
testConfig.asCapabilities(expectedBypassUserPolicyChecks),
testConfig.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
@@ -103,7 +122,9 @@
final boolean autoDetectionEnabled = false;
ConfigurationInternal initialConfigurationInternal =
createConfigurationInternal(autoDetectionEnabled);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
+ TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+ initialConfigurationInternal, testStatus);
TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
@@ -131,13 +152,15 @@
}
@Test
- public void testSuggestGeolocationTimeZone() throws Exception {
+ public void testHandleLocationAlgorithmEvent() throws Exception {
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
- mTimeZoneDetectorInternal.suggestGeolocationTimeZone(timeZoneSuggestion);
+ LocationAlgorithmEvent suggestionEvent = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
+ mTimeZoneDetectorInternal.handleLocationAlgorithmEvent(suggestionEvent);
mTestHandler.assertTotalMessagesEnqueued(1);
mTestHandler.waitForMessagesToBeProcessed();
- verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+ verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(suggestionEvent);
}
private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
return new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index d8346ee..d9d8053 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -16,6 +16,11 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
@@ -34,8 +39,11 @@
import static org.mockito.Mockito.when;
import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -59,6 +67,13 @@
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorServiceTest {
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING),
+ ARBITRARY_LOCATION_CERTAIN_STATUS);
private static final int ARBITRARY_USER_ID = 9999;
private static final List<String> ARBITRARY_TIME_ZONE_IDS = Arrays.asList("TestZoneId");
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
@@ -113,7 +128,8 @@
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(configuration,
+ ARBITRARY_DETECTOR_STATUS);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -128,6 +144,7 @@
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
new TimeZoneCapabilitiesAndConfig(
+ ARBITRARY_DETECTOR_STATUS,
configuration.asCapabilities(expectedBypassUserPolicyChecks),
configuration.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
@@ -161,7 +178,9 @@
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
+
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+ initialConfiguration, ARBITRARY_DETECTOR_STATUS);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -231,31 +250,35 @@
}
@Test
- public void testSuggestGeolocationTimeZone_withoutPermission() {
+ public void testHandleLocationAlgorithmEvent_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
assertThrows(SecurityException.class,
- () -> mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorService.handleLocationAlgorithmEvent(event));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
}
@Test
- public void testSuggestGeolocationTimeZone() throws Exception {
+ public void testHandleLocationAlgorithmEvent() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
- mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+ mTimeZoneDetectorService.handleLocationAlgorithmEvent(event);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
mTestHandler.waitForMessagesToBeProcessed();
- verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+ verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(event);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index f50e7fb..b991c5a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -16,6 +16,12 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
@@ -35,6 +41,7 @@
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.assertTrue;
@@ -47,8 +54,11 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -189,6 +199,9 @@
.setGeoDetectionEnabledSetting(true)
.build();
+ private static final TelephonyTimeZoneAlgorithmStatus TELEPHONY_ALGORITHM_RUNNING_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeEnvironment mFakeEnvironment;
private HandlerThread mHandlerThread;
@@ -233,9 +246,7 @@
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_DISABLED_GEO_DISABLED);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(0);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
@@ -244,10 +255,7 @@
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_ENABLED_GEO_ENABLED);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(1);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
@@ -258,10 +266,7 @@
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
USER_ID, requestedChanges, bypassUserPolicyChecks);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(1);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
}
}
@@ -290,11 +295,9 @@
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
otherUserId, requestedChanges, bypassUserPolicyChecks);
- mTestHandler.waitForMessagesToBeProcessed();
// Only changes to the current user's config are notified.
- stateChangeListener.assertNotificationsReceived(0);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
}
// Current user behavior: the strategy caches and returns the latest configuration.
@@ -426,9 +429,9 @@
QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion,
TELEPHONY_SCORE_NONE);
- assertEquals(expectedSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(SLOT_INDEX2, null);
assertEquals(expectedSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -439,10 +442,10 @@
QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion,
TELEPHONY_SCORE_NONE);
- assertEquals(expectedSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedSlotIndex2ScoredSuggestion);
// SlotIndex1 should always beat slotIndex2, all other things being equal.
assertEquals(expectedSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -477,8 +480,8 @@
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
lowQualitySuggestion, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -494,8 +497,8 @@
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
goodQualitySuggestion, testCase2.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -511,8 +514,8 @@
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
lowQualitySuggestion2, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -543,8 +546,8 @@
// Assert internal service state.
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -560,8 +563,8 @@
}
// Assert internal service state.
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -570,8 +573,8 @@
.verifyTimeZoneNotChanged();
// Assert internal service state.
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -622,8 +625,8 @@
}
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion);
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -677,10 +680,10 @@
}
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -690,10 +693,10 @@
script.verifyTimeZoneNotChanged();
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
// SlotIndex1 should always beat slotIndex2, all other things being equal.
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -709,20 +712,20 @@
}
// Assert internal service state.
- assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Reset the state for the next loop.
script.simulateTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion)
.verifyTimeZoneNotChanged();
- assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
}
}
@@ -866,53 +869,185 @@
}
@Test
- public void testGeoSuggestion_uncertain() {
+ public void testLocationAlgorithmEvent_statusChangesOnly() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
- GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion();
+ TimeZoneDetectorStatus expectedInitialDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ LocationTimeZoneAlgorithmStatus.UNKNOWN);
+ script.verifyCachedDetectorStatus(expectedInitialDetectorStatus);
- script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
- .verifyTimeZoneNotChanged();
+ LocationTimeZoneAlgorithmStatus algorithmStatus1 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationTimeZoneAlgorithmStatus algorithmStatus2 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertNotEquals(algorithmStatus1, algorithmStatus2);
- // Assert internal service state.
- assertEquals(uncertainSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ {
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ new LocationAlgorithmEvent(algorithmStatus1, null);
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ algorithmStatus1);
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+ }
+
+ {
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ new LocationAlgorithmEvent(algorithmStatus2, null);
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ algorithmStatus2);
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+ }
}
@Test
- public void testGeoSuggestion_noZones() {
+ public void testLocationAlgorithmEvent_uncertain() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
- GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion();
-
- script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
// Assert internal service state.
- assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ // Detector remains running and location algorithm is still uncertain so nothing to report.
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
@Test
- public void testGeoSuggestion_oneZone() {
- GeolocationTimeZoneSuggestion suggestion =
- createCertainGeolocationSuggestion("Europe/London");
-
+ public void testLocationAlgorithmEvent_noZones() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
- script.simulateGeolocationTimeZoneSuggestion(suggestion)
- .verifyTimeZoneChangedAndReset(suggestion);
+ LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
// Assert internal service state.
- assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+ }
+
+ @Test
+ public void testLocationAlgorithmEvent_oneZone() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ Script script = new Script()
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+ .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
/**
@@ -921,41 +1056,35 @@
* set to until that unambiguously can't be correct.
*/
@Test
- public void testGeoSuggestion_multiZone() {
- GeolocationTimeZoneSuggestion londonOnlySuggestion =
- createCertainGeolocationSuggestion("Europe/London");
- GeolocationTimeZoneSuggestion londonOrParisSuggestion =
- createCertainGeolocationSuggestion("Europe/Paris", "Europe/London");
- GeolocationTimeZoneSuggestion parisOnlySuggestion =
- createCertainGeolocationSuggestion("Europe/Paris");
-
+ public void testLocationAlgorithmEvent_multiZone() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
- script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
- .verifyTimeZoneChangedAndReset(londonOnlySuggestion);
- assertEquals(londonOnlySuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent londonOnlyEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
+ script.simulateLocationAlgorithmEvent(londonOnlyEvent)
+ .verifyTimeZoneChangedAndReset(londonOnlyEvent)
+ .verifyLatestLocationAlgorithmEventReceived(londonOnlyEvent);
// Confirm bias towards the current device zone when there's multiple zones to choose from.
- script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
- .verifyTimeZoneNotChanged();
- assertEquals(londonOrParisSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent londonOrParisEvent =
+ createCertainLocationAlgorithmEvent("Europe/Paris", "Europe/London");
+ script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
- script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion)
- .verifyTimeZoneChangedAndReset(parisOnlySuggestion);
- assertEquals(parisOnlySuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent parisOnlyEvent = createCertainLocationAlgorithmEvent("Europe/Paris");
+ script.simulateLocationAlgorithmEvent(parisOnlyEvent)
+ .verifyTimeZoneChangedAndReset(parisOnlyEvent)
+ .verifyLatestLocationAlgorithmEventReceived(parisOnlyEvent);
// Now the suggestion that previously left the device on Europe/London will leave the device
// on Europe/Paris.
- script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
- .verifyTimeZoneNotChanged();
- assertEquals(londonOrParisSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
}
/**
@@ -964,8 +1093,9 @@
*/
@Test
public void testChangingGeoDetectionEnabled() {
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/London");
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
"Europe/Paris");
@@ -973,20 +1103,22 @@
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
// Add suggestions. Nothing should happen as time zone detection is disabled.
- script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneNotChanged();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ // A detector status change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
- .verifyTimeZoneNotChanged();
+ .verifyTimeZoneNotChanged()
+ .verifyLatestTelephonySuggestionReceived(SLOT_INDEX1, telephonySuggestion);
- assertEquals(telephonySuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
// Toggling the time zone detection enabled setting on should cause the device setting to be
// set from the telephony signal, as we've started with geolocation time zone detection
@@ -994,18 +1126,25 @@
script.simulateSetAutoMode(true)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
// Changing the detection to enable geo detection will cause the device tz setting to
// change to use the latest geolocation suggestion.
script.simulateSetGeoDetectionEnabled(true)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion);
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
// Changing the detection to disable geo detection should cause the device tz setting to
// change to the telephony suggestion.
script.simulateSetGeoDetectionEnabled(false)
- .verifyTimeZoneChangedAndReset(telephonySuggestion);
+ .verifyTimeZoneChangedAndReset(telephonySuggestion)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
}
@Test
@@ -1039,21 +1178,20 @@
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
// Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/London");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
}
@@ -1076,22 +1214,22 @@
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Rome");
+ LocationAlgorithmEvent certainLocationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
+ createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
// No change needed, device will already be set to Europe/Rome.
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1108,21 +1246,20 @@
// Make the geolocation algorithm uncertain.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
.verifyTelephonyFallbackIsEnabled(true);
}
// Make the geolocation algorithm certain, disabling telephony fallback.
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Lisbon");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Lisbon");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
}
@@ -1130,10 +1267,9 @@
// Demonstrate what happens when geolocation is uncertain when telephony fallback is
// enabled.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false)
.simulateEnableTelephonyFallback()
@@ -1161,10 +1297,9 @@
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1172,10 +1307,9 @@
// Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
// to
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1185,17 +1319,16 @@
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Rome");
+ LocationAlgorithmEvent certainEvent =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainEvent)
+ .verifyTimeZoneChangedAndReset(certainEvent)
.verifyTelephonyFallbackIsEnabled(false);
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(uncertainEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1319,15 +1452,15 @@
TelephonyTimeZoneSuggestion telephonySuggestion =
createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, "Zone2");
- GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion =
- createCertainGeolocationSuggestion("Zone3", "Zone2");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Zone3", "Zone2");
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
.verifyTimeZoneNotChanged()
- .simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
- manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+ manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
// Update the config and confirm that the config metrics state updates also.
@@ -1336,11 +1469,11 @@
.setGeoDetectionEnabledSetting(true)
.build();
- expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
+ expectedDeviceTimeZoneId = locationAlgorithmEvent.getSuggestion().getZoneIds().get(0);
script.simulateConfigurationInternalChange(expectedInternalConfig)
.verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
- manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+ manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
}
@@ -1352,7 +1485,7 @@
ConfigurationInternal expectedInternalConfig,
String expectedDeviceTimeZoneId, ManualTimeZoneSuggestion expectedManualSuggestion,
TelephonyTimeZoneSuggestion expectedTelephonySuggestion,
- GeolocationTimeZoneSuggestion expectedGeolocationTimeZoneSuggestion,
+ LocationAlgorithmEvent expectedLocationAlgorithmEvent,
int expectedDetectionMode) {
MetricsTimeZoneDetectorState actualState = mTimeZoneDetectorStrategy.generateMetricsState();
@@ -1365,7 +1498,7 @@
MetricsTimeZoneDetectorState.create(
tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
expectedManualSuggestion, expectedTelephonySuggestion,
- expectedGeolocationTimeZoneSuggestion);
+ expectedLocationAlgorithmEvent);
// Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
assertEquals(expectedState, actualState);
}
@@ -1405,20 +1538,37 @@
return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
}
+ private LocationAlgorithmEvent createCertainLocationAlgorithmEvent(@NonNull String... zoneIds) {
+ GeolocationTimeZoneSuggestion suggestion = createCertainGeolocationSuggestion(zoneIds);
+ LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_CERTAIN, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Test certain event");
+ return event;
+ }
+
+ private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() {
+ GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion();
+ LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Test uncertain event");
+ return event;
+ }
+
private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
- return GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mFakeEnvironment.elapsedRealtimeMillis(), null);
+ return GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ mFakeEnvironment.elapsedRealtimeMillis());
}
private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
@NonNull String... zoneIds) {
assertNotNull(zoneIds);
- GeolocationTimeZoneSuggestion suggestion =
- GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
- suggestion.addDebugInfo("Test suggestion");
- return suggestion;
+ return GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
}
static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
@@ -1499,6 +1649,14 @@
}
}
+ private void assertStateChangeNotificationsSent(
+ TestStateChangeListener stateChangeListener, int expectedCount) {
+ // State change notifications are asynchronous, so we have to wait.
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceivedAndReset(expectedCount);
+ }
+
/**
* A "fluent" class allows reuse of code in tests: initialization, simulation and verification
* logic.
@@ -1516,6 +1674,11 @@
return this;
}
+ Script registerStateChangeListener(StateChangeListener stateChangeListener) {
+ mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+ return this;
+ }
+
Script simulateIncrementClock() {
mFakeEnvironment.incrementClock();
return this;
@@ -1555,11 +1718,10 @@
}
/**
- * Simulates the time zone detection strategy receiving a geolocation-originated
- * suggestion.
+ * Simulates the time zone detection strategy receiving a location algorithm event.
*/
- Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) {
- mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion);
+ Script simulateLocationAlgorithmEvent(LocationAlgorithmEvent event) {
+ mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(event);
return this;
}
@@ -1616,7 +1778,9 @@
return this;
}
- Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
+ Script verifyTimeZoneChangedAndReset(LocationAlgorithmEvent event) {
+ GeolocationTimeZoneSuggestion suggestion = event.getSuggestion();
+ assertNotNull("Only events with suggestions can change the time zone", suggestion);
assertEquals("Only use this method with unambiguous geo suggestions",
1, suggestion.getZoneIds().size());
verifyTimeZoneChangedAndReset(
@@ -1631,6 +1795,32 @@
return this;
}
+ Script verifyCachedDetectorStatus(TimeZoneDetectorStatus expectedStatus) {
+ assertEquals(expectedStatus,
+ mTimeZoneDetectorStrategy.getCachedDetectorStatusForTests());
+ return this;
+ }
+
+ Script verifyLatestLocationAlgorithmEventReceived(LocationAlgorithmEvent expectedEvent) {
+ assertEquals(expectedEvent,
+ mTimeZoneDetectorStrategy.getLatestLocationAlgorithmEvent());
+ return this;
+ }
+
+ Script verifyLatestTelephonySuggestionReceived(int slotIndex,
+ TelephonyTimeZoneSuggestion expectedSuggestion) {
+ assertEquals(expectedSuggestion,
+ mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex).suggestion);
+ return this;
+ }
+
+ Script verifyLatestQualifiedTelephonySuggestionReceived(int slotIndex,
+ QualifiedTelephonyTimeZoneSuggestion expectedQualifiedSuggestion) {
+ assertEquals(expectedQualifiedSuggestion,
+ mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
+ return this;
+ }
+
Script resetConfigurationTracking() {
mFakeEnvironment.commitAllChanges();
return this;
@@ -1671,11 +1861,16 @@
mNotificationsReceived++;
}
- public void resetNotificationsReceivedCount() {
+ public void assertNotificationsReceivedAndReset(int expectedCount) {
+ assertNotificationsReceived(expectedCount);
+ resetNotificationsReceivedCount();
+ }
+
+ private void resetNotificationsReceivedCount() {
mNotificationsReceived = 0;
}
- public void assertNotificationsReceived(int expectedCount) {
+ private void assertNotificationsReceived(int expectedCount) {
assertEquals(expectedCount, mNotificationsReceived);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
index 34d0082..f8d169b 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -31,9 +31,9 @@
@Override
public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
if (mIsUncertain) {
+ TimeZoneProviderStatus timeZoneProviderStatus = null;
return TimeZoneProviderEvent.createUncertainEvent(
- timeZoneProviderEvent.getCreationElapsedMillis(),
- TimeZoneProviderStatus.UNKNOWN);
+ timeZoneProviderEvent.getCreationElapsedMillis(), timeZoneProviderStatus);
}
return timeZoneProviderEvent;
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index ed426cd..7b1db95 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -15,11 +15,13 @@
*/
package com.android.server.timezonedetector.location;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
@@ -42,6 +44,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -51,6 +54,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
@@ -60,6 +64,7 @@
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.TestState;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
@@ -87,9 +92,9 @@
createSuggestionEvent(asList("Europe/Paris"));
private static final TimeZoneProviderStatus UNCERTAIN_PROVIDER_STATUS =
new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_UNKNOWN)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
.build();
private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
TimeZoneProviderEvent.createUncertainEvent(
@@ -141,7 +146,7 @@
mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true);
// Initialize. After initialization the providers must be initialized and one should be
- // started.
+ // started. They should report their status change via the callback.
controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
@@ -154,7 +159,8 @@
mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -184,7 +190,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -211,7 +218,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING, STATE_FAILED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -239,7 +247,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -262,7 +271,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -282,7 +292,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -296,7 +307,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing with no provider event being received from either the primary or
@@ -311,7 +322,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Finally, the uncertainty timeout should cause the controller to make an uncertain
@@ -324,7 +335,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit();
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -345,7 +356,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -358,7 +370,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -380,7 +392,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -392,7 +405,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the primary provider. This should cause a
@@ -405,7 +418,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -427,7 +440,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -439,7 +453,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -453,7 +467,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -475,7 +489,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -488,7 +503,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -501,7 +516,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
@@ -513,7 +528,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -535,7 +550,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -547,7 +563,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -561,7 +577,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -575,7 +591,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
@@ -588,7 +604,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -610,7 +626,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -623,7 +640,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -639,7 +656,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -654,7 +671,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -670,7 +687,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing. This means the uncertainty timeout should fire and the uncertain
@@ -683,7 +700,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
- mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -705,7 +722,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -718,7 +736,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -733,7 +751,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make another
@@ -747,7 +765,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -767,7 +785,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -778,7 +797,8 @@
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -788,7 +808,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -807,7 +828,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -818,7 +840,8 @@
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a success event being received from the primary provider.
@@ -830,7 +853,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -843,8 +866,9 @@
assertControllerState(controller, STATE_STOPPED);
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN, STATE_STOPPED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -865,7 +889,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
@@ -879,7 +904,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -897,9 +922,9 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestMetricsLogger.assertStateChangesAndCommit(
- STATE_UNCERTAIN, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_INITIALIZING);
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -920,7 +945,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
@@ -933,7 +959,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate uncertainty from the secondary.
@@ -945,7 +971,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the secondary provider should cause the controller to make
@@ -958,7 +984,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -971,7 +997,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@@ -992,7 +1018,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
@@ -1005,7 +1032,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -1015,7 +1042,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -1026,7 +1054,8 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1047,7 +1076,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1062,7 +1092,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1074,7 +1104,7 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make
@@ -1087,7 +1117,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -1100,7 +1130,7 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@@ -1121,7 +1151,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1136,7 +1167,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1148,7 +1179,7 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Now signal a config change so that geo detection is disabled.
@@ -1158,7 +1189,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled. Only the primary can be
@@ -1170,7 +1202,8 @@
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1191,7 +1224,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure event from the primary. This will start the secondary.
@@ -1203,7 +1237,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate failure event from the secondary.
@@ -1214,7 +1248,8 @@
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_FAILED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1233,7 +1268,7 @@
{
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_INITIALIZING, state.getControllerState());
- assertNull(state.getLastSuggestion());
+ assertNull(state.getLastEvent().getSuggestion());
assertControllerRecordedStates(state,
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
assertProviderStates(state.getPrimaryProviderStates(),
@@ -1251,7 +1286,7 @@
{
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_INITIALIZING, state.getControllerState());
- assertNull(state.getLastSuggestion());
+ assertNull(state.getLastEvent().getSuggestion());
assertControllerRecordedStates(state);
assertProviderStates(
state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN);
@@ -1268,7 +1303,7 @@
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_CERTAIN, state.getControllerState());
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
- state.getLastSuggestion().getZoneIds());
+ state.getLastEvent().getSuggestion().getZoneIds());
assertControllerRecordedStates(state, STATE_CERTAIN);
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(
@@ -1280,7 +1315,7 @@
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_CERTAIN, state.getControllerState());
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
- state.getLastSuggestion().getZoneIds());
+ state.getLastEvent().getSuggestion().getZoneIds());
assertControllerRecordedStates(state);
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(state.getSecondaryProviderStates());
@@ -1313,7 +1348,8 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
@@ -1327,7 +1363,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -1335,11 +1371,11 @@
controller.destroy();
assertControllerState(controller, STATE_DESTROYED);
- mTestMetricsLogger.assertStateChangesAndCommit(
- STATE_UNCERTAIN, STATE_STOPPED, STATE_DESTROYED);
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_DESTROYED);
// Confirm that the previous suggestion was overridden.
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(
PROVIDER_STATE_STOPPED, PROVIDER_STATE_DESTROYED);
@@ -1405,9 +1441,9 @@
private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List<String> timeZoneIds) {
TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
- .setConnectivityStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
.build();
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
.setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
@@ -1517,63 +1553,101 @@
private static class TestCallback extends LocationTimeZoneProviderController.Callback {
- private TestState<GeolocationTimeZoneSuggestion> mLatestSuggestion = new TestState<>();
+ private TestState<LocationAlgorithmEvent> mLatestEvent = new TestState<>();
TestCallback(ThreadingDomain threadingDomain) {
super(threadingDomain);
}
@Override
- void suggest(GeolocationTimeZoneSuggestion suggestion) {
- mLatestSuggestion.set(suggestion);
+ void sendEvent(LocationAlgorithmEvent event) {
+ mLatestEvent.set(event);
}
- void assertCertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ void assertNoEventReported() {
+ mLatestEvent.assertHasNotBeenSet();
+ }
+
+ /**
+ * Asserts one or more events have been reported, and the most recent does not contain a
+ * suggestion.
+ */
+ void assertEventWithNoSuggestionReportedAndCommit(
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus) {
+ mLatestEvent.assertHasBeenSet();
+
+ LocationAlgorithmEvent latest = mLatestEvent.getLatest();
+ assertEquals(expectedAlgorithmStatus, latest.getAlgorithmStatus().getStatus());
+ assertNull(latest.getSuggestion());
+ mLatestEvent.commitLatest();
+ }
+
+ void assertEventWithCertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION, event.getType());
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
TimeZoneProviderSuggestion suggestion = event.getSuggestion();
- assertSuggestionMadeAndCommit(
+ assertEventWithSuggestionReportedAndCommit(
+ expectedAlgorithmStatus,
suggestion.getElapsedRealtimeMillis(),
suggestion.getTimeZoneIds());
}
- void assertNoSuggestionMade() {
- mLatestSuggestion.assertHasNotBeenSet();
- }
-
- /** Asserts that an uncertain suggestion has been made from the supplied event. */
- void assertUncertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ /**
+ * Asserts that one or more events have been reported, and the most recent contains an
+ * uncertain suggestion matching select details from the supplied provider event.
+ */
+ void assertEventWithUncertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN, event.getType());
- assertSuggestionMadeAndCommit(event.getCreationElapsedMillis(), null);
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
+ assertEventWithSuggestionReportedAndCommit(
+ expectedAlgorithmStatus, event.getCreationElapsedMillis(), null);
}
/**
- * Asserts that an uncertain suggestion has been made.
- * Ignores the suggestion's effectiveFromElapsedMillis.
+ * Asserts that one or more events have been reported, and the most recent contains an
+ * uncertain suggestion. Ignores the suggestion's effectiveFromElapsedMillis.
*/
- void assertUncertainSuggestionMadeAndCommit() {
+ void assertEventWithUncertainSuggestionReportedAndCommit() {
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
+
// An "uncertain" suggestion has null time zone IDs.
- assertSuggestionMadeAndCommit(null, null);
+ assertEventWithSuggestionReportedAndCommit(expectedAlgorithmStatus, null, null);
}
/**
- * Asserts that a suggestion has been made and some properties of that suggestion.
- * When expectedEffectiveFromElapsedMillis is null then its value isn't checked.
+ * Asserts that an event has been reported containing a suggestion and some properties of
+ * that suggestion. When expectedEffectiveFromElapsedMillis is null then its value isn't
+ * checked.
*/
- private void assertSuggestionMadeAndCommit(
+ private void assertEventWithSuggestionReportedAndCommit(
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus,
@Nullable @ElapsedRealtimeLong Long expectedEffectiveFromElapsedMillis,
@Nullable List<String> expectedZoneIds) {
- mLatestSuggestion.assertHasBeenSet();
+ mLatestEvent.assertHasBeenSet();
+
+ LocationAlgorithmEvent latestEvent = mLatestEvent.getLatest();
+ assertEquals(expectedAlgorithmStatus, latestEvent.getAlgorithmStatus().getStatus());
+
+ GeolocationTimeZoneSuggestion suggestion = latestEvent.getSuggestion();
+ assertNotNull("Latest event doesn't contain a suggestion: event=" + latestEvent,
+ suggestion);
+
if (expectedEffectiveFromElapsedMillis != null) {
- assertEquals(
- expectedEffectiveFromElapsedMillis.longValue(),
- mLatestSuggestion.getLatest().getEffectiveFromElapsedMillis());
+ assertEquals(expectedEffectiveFromElapsedMillis.longValue(),
+ suggestion.getEffectiveFromElapsedMillis());
}
- assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
- mLatestSuggestion.commitLatest();
+ assertEquals(expectedZoneIds, suggestion.getZoneIds());
+ mLatestEvent.commitLatest();
}
}
@@ -1598,11 +1672,9 @@
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mInitialized = true;
- if (mFailDuringInitialization) {
- throw new RuntimeException("Simulated initialization failure");
- }
+ return !mFailDuringInitialization;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index 8429fa4..1ae74c6 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -15,6 +15,9 @@
*/
package com.android.server.timezonedetector.location;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
@@ -57,6 +60,12 @@
public class LocationTimeZoneProviderTest {
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 123456789L;
+ private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
private TestThreadingDomain mTestThreadingDomain;
private TestProviderListener mProviderListener;
@@ -80,91 +89,108 @@
mTimeZoneProviderEventPreProcessor);
// initialize()
- provider.initialize(mProviderListener);
- provider.assertOnInitializeCalled();
+ {
+ provider.initialize(mProviderListener);
+ provider.assertOnInitializeCalled();
- ProviderState currentState = assertAndReturnProviderState(
- provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
- assertNull(currentState.currentUserConfiguration);
- assertSame(provider, currentState.provider);
- mTestThreadingDomain.assertQueueEmpty();
+ ProviderState currentState = assertAndReturnProviderState(
+ provider, providerMetricsLogger, PROVIDER_STATE_STOPPED,
+ /*expectedReportedStatus=*/null);
+ assertNull(currentState.currentUserConfiguration);
+ assertSame(provider, currentState.provider);
+ mTestThreadingDomain.assertQueueEmpty();
+ }
+
+ ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
// startUpdates()
- ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
- Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
- Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
- Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3);
- provider.startUpdates(config, arbitraryInitializationTimeout,
- arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold);
+ {
+ Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+ Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3);
+ provider.startUpdates(config, arbitraryInitializationTimeout,
+ arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold);
- provider.assertOnStartCalled(
- arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold);
+ provider.assertOnStartCalled(
+ arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold);
- currentState = assertAndReturnProviderState(
- provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING);
- assertSame(provider, currentState.provider);
- assertEquals(config, currentState.currentUserConfiguration);
- assertNull(currentState.event);
- // The initialization timeout should be queued.
- Duration expectedInitializationTimeout =
- arbitraryInitializationTimeout.plus(arbitraryInitializationTimeoutFuzz);
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedInitializationTimeout);
- // We don't intend to trigger the timeout, so clear it.
- mTestThreadingDomain.removeAllQueuedRunnables();
+ ProviderState currentState = assertAndReturnProviderState(
+ provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING,
+ /*expectedReportedStatus=*/null);
+ assertSame(provider, currentState.provider);
+ assertEquals(config, currentState.currentUserConfiguration);
+ assertNull(currentState.event);
+ // The initialization timeout should be queued.
+ Duration expectedInitializationTimeout =
+ arbitraryInitializationTimeout.plus(arbitraryInitializationTimeoutFuzz);
+ mTestThreadingDomain.assertSingleDelayedQueueItem(expectedInitializationTimeout);
+ // We don't intend to trigger the timeout, so clear it.
+ mTestThreadingDomain.removeAllQueuedRunnables();
- // Entering started does not trigger an onProviderStateChanged() as it is requested by the
- // controller.
- mProviderListener.assertProviderChangeNotReported();
+ // Entering started does not trigger an onProviderStateChanged() as it is requested by
+ // the controller.
+ mProviderListener.assertProviderChangeNotReported();
+ }
// Simulate a suggestion event being received.
- TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
- .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
- .setTimeZoneIds(Arrays.asList("Europe/London"))
- .build();
- TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
- TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
- ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, providerStatus);
- provider.simulateProviderEventReceived(event);
+ {
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .setTimeZoneIds(Arrays.asList("Europe/London"))
+ .build();
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, ARBITRARY_PROVIDER_STATUS);
+ provider.simulateProviderEventReceived(event);
- currentState = assertAndReturnProviderState(
- provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN);
- assertSame(provider, currentState.provider);
- assertEquals(event, currentState.event);
- assertEquals(config, currentState.currentUserConfiguration);
- mTestThreadingDomain.assertQueueEmpty();
- mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
+ ProviderState currentState = assertAndReturnProviderState(
+ provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN,
+ ARBITRARY_PROVIDER_STATUS);
+ assertSame(provider, currentState.provider);
+ assertEquals(event, currentState.event);
+ assertEquals(config, currentState.currentUserConfiguration);
+ mTestThreadingDomain.assertQueueEmpty();
+ mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
+ }
// Simulate an uncertain event being received.
- event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
- TimeZoneProviderStatus.UNKNOWN);
- provider.simulateProviderEventReceived(event);
+ {
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_PROVIDER_STATUS);
+ provider.simulateProviderEventReceived(event);
- currentState = assertAndReturnProviderState(
- provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN);
- assertSame(provider, currentState.provider);
- assertEquals(event, currentState.event);
- assertEquals(config, currentState.currentUserConfiguration);
- mTestThreadingDomain.assertQueueEmpty();
- mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_UNCERTAIN);
+ ProviderState currentState = assertAndReturnProviderState(
+ provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN,
+ ARBITRARY_PROVIDER_STATUS);
+ assertSame(provider, currentState.provider);
+ assertEquals(event, currentState.event);
+ assertEquals(config, currentState.currentUserConfiguration);
+ mTestThreadingDomain.assertQueueEmpty();
+ mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_UNCERTAIN);
+ }
// stopUpdates()
- provider.stopUpdates();
- provider.assertOnStopUpdatesCalled();
+ {
+ provider.stopUpdates();
+ provider.assertOnStopUpdatesCalled();
- currentState = assertAndReturnProviderState(
- provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
- assertSame(provider, currentState.provider);
- assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
- assertNull(currentState.event);
- assertNull(currentState.currentUserConfiguration);
- mTestThreadingDomain.assertQueueEmpty();
- // Entering stopped does not trigger an onProviderStateChanged() as it is requested by the
- // controller.
- mProviderListener.assertProviderChangeNotReported();
+ ProviderState currentState = assertAndReturnProviderState(
+ provider, providerMetricsLogger, PROVIDER_STATE_STOPPED,
+ /*expectedReportedStatus=*/null);
+ assertSame(provider, currentState.provider);
+ assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
+ assertNull(currentState.event);
+ assertNull(currentState.currentUserConfiguration);
+ mTestThreadingDomain.assertQueueEmpty();
+ // Entering stopped does not trigger an onProviderStateChanged() as it is requested by
+ // the controller.
+ mProviderListener.assertProviderChangeNotReported();
+ }
// destroy()
- provider.destroy();
- provider.assertOnDestroyCalled();
+ {
+ provider.destroy();
+ provider.assertOnDestroyCalled();
+ }
}
@Test
@@ -196,13 +222,12 @@
.setTimeZoneIds(Arrays.asList("Europe/London"))
.build();
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
- ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, TimeZoneProviderStatus.UNKNOWN);
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, null);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
// Simulate an uncertain event being received.
- event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
- TimeZoneProviderStatus.UNKNOWN);
+ event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS, null);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
@@ -239,7 +264,7 @@
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.setTimeZoneIds(invalidTimeZoneIds)
.build();
- TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
+ TimeZoneProviderStatus providerStatus = null;
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
ARBITRARY_ELAPSED_REALTIME_MILLIS, invalidIdSuggestion, providerStatus);
provider.simulateProviderEventReceived(event);
@@ -275,9 +300,11 @@
*/
private static ProviderState assertAndReturnProviderState(
TestLocationTimeZoneProvider provider,
- RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum) {
+ RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum,
+ TimeZoneProviderStatus expectedReportedStatus) {
ProviderState currentState = provider.getCurrentState();
assertEquals(expectedStateEnum, currentState.stateEnum);
+ assertEquals(expectedReportedStatus, currentState.getReportedStatus());
providerMetricsLogger.assertChangeLoggedAndRemove(expectedStateEnum);
providerMetricsLogger.assertNoMoreLogEntries();
return currentState;
@@ -303,8 +330,9 @@
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mOnInitializeCalled = true;
+ return true;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index c478604..f3440f7 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -16,9 +16,9 @@
package com.android.server.timezonedetector.location;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -61,7 +61,7 @@
TimeZoneProviderStatus expectedProviderStatus =
new TimeZoneProviderStatus.Builder(event.getTimeZoneProviderStatus())
- .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
.build();
TimeZoneProviderEvent expectedResultEvent =
@@ -75,9 +75,9 @@
private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
- .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
- .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
.build();
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
.setTimeZoneIds(Arrays.asList(timeZoneIds))
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
new file mode 100644
index 0000000..a4041b7
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "FakeMediaApp",
+
+ sdk_version: "current",
+
+ srcs: ["**/*.java"],
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
similarity index 62%
copy from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
copy to services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
index bafe4c4..c08ee7a 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- 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.
@@ -13,15 +13,19 @@
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.frameworks.servicestests.install_uses_sdk">
- <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
- <!-- This fails because 31 is not version 5 -->
- <extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
- <extension-sdk android:sdkVersion="31" android:minExtensionVersion="5" />
- </uses-sdk>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.fakemediaapp">
<application>
+ <receiver
+ android:name=".FakeMediaButtonBroadcastReceiver"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
+ </intent-filter>
+ </receiver>
</application>
+
</manifest>
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..41f0cf5
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.servicestests.apps.fakemediaapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class FakeMediaButtonBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "FakeMediaButtonBroadcastReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(TAG, "onReceive not expected");
+ }
+}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
index 3e79407..b8585f2 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
@@ -34,7 +34,8 @@
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "Test job executing: " + params.getJobId());
Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
- reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+ reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
sendBroadcast(reportJobStartIntent);
return true;
}
@@ -43,7 +44,8 @@
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "Test job stopped executing: " + params.getJobId());
Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
- reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+ reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
sendBroadcast(reportJobStopIntent);
// Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
return false;
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 2ed8b10..afec085 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1101,6 +1101,10 @@
new NotificationChannel("id", "name", IMPORTANCE_HIGH);
mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
+ // pretend only this following part is called by the app (system permissions are required to
+ // update the notification channel on behalf of the user above)
+ mService.isSystemUid = false;
+
// Recreating with a lower importance leaves channel unchanged.
final NotificationChannel dupeChannel =
new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW);
@@ -1126,6 +1130,46 @@
}
@Test
+ public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception {
+ // Confirm that when createNotificationChannels is called from the relevant app and not
+ // system, then it cannot set fields that can't be set by apps
+ mService.isSystemUid = false;
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ channel.setBypassDnd(true);
+ channel.setAllowBubbles(true);
+
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+
+ final NotificationChannel createdChannel =
+ mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+ assertFalse(createdChannel.canBypassDnd());
+ assertFalse(createdChannel.canBubble());
+ }
+
+ @Test
+ public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception {
+ // Confirm that when createNotificationChannels is called from system,
+ // then it can set fields that can't be set by apps
+ mService.isSystemUid = true;
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ channel.setBypassDnd(true);
+ channel.setAllowBubbles(true);
+
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+
+ final NotificationChannel createdChannel =
+ mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+ assertTrue(createdChannel.canBypassDnd());
+ assertTrue(createdChannel.canBubble());
+ }
+
+ @Test
public void testBlockedNotifications_suspended() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
@@ -3088,6 +3132,8 @@
@Test
public void testDeleteChannelGroupChecksForFgses() throws Exception {
+ // the setup for this test requires it to seem like it's coming from the app
+ mService.isSystemUid = false;
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
.thenReturn(singletonList(mock(AssociationInfo.class)));
CountDownLatch latch = new CountDownLatch(2);
@@ -3100,7 +3146,7 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.createNotificationChannels(PKG, pls);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -3119,8 +3165,10 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
- mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ // Because existing channels won't have their groups overwritten when the call
+ // is from the app, this call won't take the channel out of the group
+ mBinderService.createNotificationChannels(PKG, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -7623,8 +7671,30 @@
}
@Test
+ public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception {
+ // The multi-user case: where the calling uid doesn't match the system uid, but the calling
+ // *appid* is the system.
+ mService.isSystemUid = false;
+ mService.isSystemAppId = true;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+ // verify that zen mode helper gets passed in a package name of "android"
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+ }
+
+ @Test
public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
mService.isSystemUid = false;
+ mService.isSystemAppId = false;
ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
.thenReturn(true);
@@ -8659,7 +8729,7 @@
assertEquals("friend", friendChannel.getConversationId());
assertEquals(null, original.getConversationId());
assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
- assertFalse(friendChannel.canBubble()); // can't be modified by app
+ assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system
assertFalse(original.getId().equals(friendChannel.getId()));
assertNotNull(friendChannel.getId());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0f93598..b64b281 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2727,7 +2727,7 @@
@Test
public void testCreateChannel_addToGroup() {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
@@ -3177,8 +3177,8 @@
@Test
public void testGetNotificationChannelGroupWithChannels() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
- NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
+ NotificationChannelGroup other = new NotificationChannelGroup("something else", "name");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, other, true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 8cf74fb..61a6985 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -32,6 +32,7 @@
public class TestableNotificationManagerService extends NotificationManagerService {
int countSystemChecks = 0;
boolean isSystemUid = true;
+ boolean isSystemAppId = true;
int countLogSmartSuggestionsVisible = 0;
Set<Integer> mChannelToastsSent = new HashSet<>();
@@ -58,6 +59,12 @@
}
@Override
+ protected boolean isCallingAppIdSystem() {
+ countSystemChecks++;
+ return isSystemUid || isSystemAppId;
+ }
+
+ @Override
protected boolean isCallerSystemOrPhone() {
countSystemChecks++;
return isSystemUid;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 376399a..85c4975 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -324,7 +324,7 @@
// The activity reports fully drawn before windows drawn, then the fully drawn event will
// be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}).
- mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false);
+ mActivityMetricsLogger.notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
notifyTransitionStarting(mTopActivity);
// The pending fully drawn event should send when the actual windows drawn event occurs.
final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
@@ -337,7 +337,7 @@
verifyNoMoreInteractions(mLaunchObserver);
final ActivityMetricsLogger.TransitionInfoSnapshot fullyDrawnInfo = mActivityMetricsLogger
- .logAppTransitionReportedDrawn(mTopActivity, false /* restoredFromBundle */);
+ .notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
assertWithMessage("Invisible event must be dropped").that(fullyDrawnInfo).isNull();
}
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 3a8e1cc..079897b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2284,8 +2284,7 @@
doReturn(false).when(mAtm).shouldDisableNonVrUiLocked();
spyOn(mDisplayContent.mDwpcHelper);
- doReturn(false).when(mDisplayContent.mDwpcHelper).isWindowingModeSupported(
- WINDOWING_MODE_PINNED);
+ doReturn(false).when(mDisplayContent.mDwpcHelper).isEnteringPipAllowed(anyInt());
assertFalse(activity.checkEnterPictureInPictureState("TEST", false /* beforeStopping */));
}
@@ -2807,7 +2806,7 @@
final Task task = activity.getTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.setVisible(false);
- task.positionChildAt(topActivity, POSITION_TOP);
+ task.positionChildAt(POSITION_TOP, topActivity, false /* includeParents */);
activity.addStartingWindow(mPackageName, android.R.style.Theme, null, true, true, false,
true, false, false, false);
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 053718e..fc1989e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.Activity.RESULT_CANCELED;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CANCELED;
@@ -609,10 +610,9 @@
@Test
public void testBackgroundActivityStartsAllowed_noStartsAborted() {
doReturn(true).when(mAtm).isBackgroundActivityStartsEnabled();
-
runAndVerifyBackgroundActivityStartsSubtest("allowed_noStartsAborted", false,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
}
@@ -621,97 +621,220 @@
* disallowed.
*/
@Test
- public void testBackgroundActivityStartsDisallowed_unsupportedStartsAborted() {
+ public void testBackgroundActivityStartsDisallowed_unsupportedUsecaseAborted() {
doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
-
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_unsupportedUsecase_aborted", true,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that unsupported usecases are aborted when background starts are
+ * disallowed.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_callingUidProcessStateTopAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingUidProcessStateTop_aborted", true,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that unsupported usecases are aborted when background starts are
+ * disallowed.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_realCallingUidProcessStateTopAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_realCallingUidProcessStateTop_aborted", true,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP,
false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that unsupported usecases are aborted when background starts are
+ * disallowed.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_hasForegroundActivitiesAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_hasForegroundActivities_aborted", true,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
true, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that unsupported usecases are aborted when background starts are
+ * disallowed.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_pinnedSingleInstanceAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_pinned_singleinstance_aborted", true,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false, true);
-
}
/**
* This test ensures that supported usecases aren't aborted when background starts are
- * disallowed.
- * The scenarios each have only one condition that makes them supported.
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the calling process runs as ROOT_UID.
*/
@Test
- public void testBackgroundActivityStartsDisallowed_supportedStartsNotAborted() {
+ public void testBackgroundActivityStartsDisallowed_rootUidNotAborted() {
doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
-
runAndVerifyBackgroundActivityStartsSubtest("disallowed_rootUid_notAborted", false,
- Process.ROOT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ Process.ROOT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the calling process is running as SYSTEM_UID.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_systemUidNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest("disallowed_systemUid_notAborted", false,
- Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ Process.SYSTEM_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the calling process is running as NFC_UID.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_nfcUidNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
- Process.NFC_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ Process.NFC_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the calling process has a visible window.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_callingUidHasVisibleWindowNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingUidHasVisibleWindow_notAborted", false,
- UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, true, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the real calling process (pending intent) has a visible window.
+ */
+ @Test
+ public void
+ testBackgroundActivityStartsDisallowed_realCallingUidHasVisibleWindowNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_realCallingUidHasVisibleWindow_notAborted", false,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, true, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, true, PROCESS_STATE_BOUND_TOP,
+ false, false, false, false, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the caller is in the recent activity list.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_callerIsRecentsNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callerIsRecents_notAborted", false,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, true, false, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the caller is temporarily (10s) allowed to start.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_callerIsAllowedNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callerIsAllowed_notAborted", false,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, true, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the caller explicitly has background activity start privilege.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_callerIsInstrumentingWithBASPnotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callerIsInstrumentingWithBackgroundActivityStartPrivileges_notAborted",
false,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, true, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the caller is a device owner.
+ */
+ @Test
+ public void
+ testBackgroundActivityStartsDisallowed_callingPackageNameIsDeviceOwnerNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingPackageNameIsDeviceOwner_notAborted", false,
- UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, true);
+ }
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the caller is an IME.
+ */
+ @Test
+ public void testBackgroundActivityStartsDisallowed_callingPackageNameIsImeNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
setupImeWindow();
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingPackageNameIsIme_notAborted", false,
- CURRENT_IME_UID, false, PROCESS_STATE_TOP + 1,
- UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ CURRENT_IME_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false);
-
}
private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
@@ -1139,6 +1262,26 @@
}
@Test
+ public void testRecycleTaskWakeUpWhenDreaming() {
+ doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ doReturn(true).when(mWm.mAtmService).isDreaming();
+ final ActivityStarter starter = prepareStarter(0 /* flags */);
+ final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ starter.mStartActivity = target;
+ target.mVisibleRequested = false;
+ target.setTurnScreenOn(true);
+ // Assume the flag was consumed by relayout.
+ target.setCurrentLaunchCanTurnScreenOn(false);
+ startActivityInner(starter, target, null /* source */, null /* options */,
+ null /* inTask */, null /* inTaskFragment */);
+ // The flag should be set again when resuming (from recycleTask) the target as top.
+ assertTrue(target.currentLaunchCanTurnScreenOn());
+ // In real case, dream activity has a higher priority (TaskDisplayArea#getPriority) that
+ // will be put at a higher z-order. So it relies on wakeUp() to be dismissed.
+ verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ }
+
+ @Test
public void testTargetTaskInSplitScreen() {
final ActivityStarter starter =
prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0332c4b..43e79f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
@@ -56,6 +57,7 @@
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.gui.DropInputMode;
import android.os.Binder;
import android.os.IBinder;
@@ -918,7 +920,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -935,11 +937,77 @@
}
@Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is not embedded.
+ assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation is not run by the remote handler because the activity is filling the Task.
+ assertFalse(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is embedded.
+ taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ final Rect embeddedBounds = new Rect(task.getBounds());
+ embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
+ taskFragment.setBounds(embeddedBounds);
+ assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation run by the remote handler.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing non-embedded activity.
final ActivityRecord closingActivity = createActivityRecord(task);
@@ -964,7 +1032,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing TaskFragment with embedded activity.
final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -991,7 +1059,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing activity in Task1.
final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
@@ -1015,7 +1083,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1043,7 +1111,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1069,7 +1137,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activities, one is trusted embedded, and the other
// one is untrusted embedded.
@@ -1128,7 +1196,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with only trusted embedded activity
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1168,7 +1236,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with only trusted embedded activity
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1259,7 +1327,7 @@
}
/** Registers remote animation for the organizer. */
- private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, int taskId,
+ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
TestRemoteAnimationRunner remoteAnimationRunner) {
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
remoteAnimationRunner, 10, 1);
@@ -1268,9 +1336,10 @@
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
- mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, taskId,
- definition);
+ mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
}
private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 32c95fa..8cfe503 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -49,6 +49,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
@@ -67,11 +68,14 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.view.animation.Animation;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
+import com.android.internal.policy.TransitionAnimation;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -495,6 +499,80 @@
assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
}
+ @Test
+ public void testGetNextAppTransitionBackgroundColor() {
+ assumeFalse(WindowManagerService.sEnableShellTransitions);
+
+ // No override by default.
+ assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+
+ // Override with a custom color.
+ mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ final int testColor = 123;
+ mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
+ 0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
+ false /* overrideTaskTransaction */);
+
+ assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Override with ActivityEmbedding remote animation. Background color should be kept.
+ mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+ false /* sync */, true /* isActivityEmbedding */);
+
+ assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Background color should not be cleared anymore after #clear().
+ mDc.mAppTransition.clear();
+ assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+ }
+
+ @Test
+ public void testGetNextAppRequestedAnimation() {
+ assumeFalse(WindowManagerService.sEnableShellTransitions);
+ final String packageName = "testPackage";
+ final int enterAnimResId = 1;
+ final int exitAnimResId = 2;
+ final int testColor = 123;
+ final Animation enterAnim = mock(Animation.class);
+ final Animation exitAnim = mock(Animation.class);
+ final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
+ spyOn(transitionAnimation);
+ doReturn(enterAnim).when(transitionAnimation)
+ .loadAppTransitionAnimation(packageName, enterAnimResId);
+ doReturn(exitAnim).when(transitionAnimation)
+ .loadAppTransitionAnimation(packageName, exitAnimResId);
+
+ // No override by default.
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+
+ // Override with a custom animation.
+ mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
+ testColor, null /* startedCallback */, null /* endedCallback */,
+ false /* overrideTaskTransaction */);
+
+ assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Override with ActivityEmbedding remote animation. Custom animation should be kept.
+ mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+ false /* sync */, true /* isActivityEmbedding */);
+
+ assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Custom animation should not be cleared anymore after #clear().
+ mDc.mAppTransition.clear();
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ }
+
private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
boolean mCancelled = false;
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 52af8ad..d99946f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -45,6 +45,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
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.Mockito.mock;
@@ -375,10 +376,10 @@
displayPolicy.setCanSystemBarsBeShownByUser(false);
displayPolicy.requestTransientBars(windowState, true);
- verify(controlTarget, never()).showInsets(anyInt(), anyBoolean());
+ verify(controlTarget, never()).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
displayPolicy.setCanSystemBarsBeShownByUser(true);
displayPolicy.requestTransientBars(windowState, true);
- verify(controlTarget).showInsets(anyInt(), anyBoolean());
+ verify(controlTarget).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index 21197ba..db1d15a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -246,5 +246,10 @@
public boolean canShowTasksInRecents() {
return true;
}
+
+ @Override
+ public boolean isEnteringPipAllowed(int uid) {
+ return true;
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index ac3d0f0..75c5b6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -213,7 +213,7 @@
assertThat(newTaskBounds).isEqualTo(newDagBounds);
// Activity config bounds is unchanged, size compat bounds is (860x[860x860/1200=616])
- assertThat(mFirstActivity.getSizeCompatScale()).isLessThan(1f);
+ assertThat(mFirstActivity.getCompatScale()).isLessThan(1f);
assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width());
assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height());
assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index 13ebc93..0568b38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -36,6 +37,8 @@
import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,12 +53,18 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class FrameRateSelectionPriorityTests extends WindowTestsBase {
- private static final float FLOAT_TOLERANCE = 0.01f;
private static final int LOW_MODE_ID = 3;
private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class);
private RefreshRatePolicy mRefreshRatePolicy;
private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+ private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+ private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+ private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
+ new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
+ new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
WindowState createWindow(String name) {
WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -85,12 +94,12 @@
assertNotNull("Window state is created", appWindow);
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority doesn't change.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -109,16 +118,15 @@
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
.getPreferredModeId(appWindow), 0);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
- assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
- .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE);
-
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
+ assertFalse(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+ .updateFrameRateVote(appWindow));
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority stays MAX_VALUE.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -127,7 +135,7 @@
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes to 1.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), 1);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
@@ -138,27 +146,27 @@
public void testApplicationInFocusWithModeId() {
final WindowState appWindow = createWindow("appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Application is in focus.
appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 0);
- assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
// Remove the mode ID request.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Verify we called actions on Transactions correctly.
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
@@ -175,7 +183,7 @@
public void testApplicationNotInFocusWithModeId() {
final WindowState appWindow = createWindow("appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
final WindowState inFocusWindow = createWindow("inFocus");
appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -183,14 +191,14 @@
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 2);
- assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -204,7 +212,7 @@
public void testApplicationNotInFocusWithoutModeId() {
final WindowState appWindow = createWindow("appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
final WindowState inFocusWindow = createWindow("inFocus");
appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -212,14 +220,14 @@
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Make sure that the mode ID is not set.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority doesn't change.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -237,11 +245,10 @@
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
- assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
- assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+ assertEquals(FRAME_RATE_VOTE_60_EXACT, appWindow.mFrameRateVote);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -262,19 +269,19 @@
.thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Remove the mode ID request.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -292,11 +299,10 @@
appWindow.mAttrs.preferredRefreshRate = 60;
assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
- assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
- assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+ assertEquals(FRAME_RATE_VOTE_60_PREFERRED, appWindow.mFrameRateVote);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -307,6 +313,6 @@
any(SurfaceControl.class), anyInt());
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
- Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+ Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index c839d12..a26cad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -55,7 +55,7 @@
mDisplayContent.setImeControlTarget(popup);
mDisplayContent.setImeLayeringTarget(appWin);
popup.mAttrs.format = PixelFormat.TRANSPARENT;
- mImeProvider.scheduleShowImePostLayout(appWin);
+ mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */);
assertTrue(mImeProvider.isReadyToShowIme());
}
@@ -64,7 +64,7 @@
WindowState target = createWindow(null, TYPE_APPLICATION, "app");
mDisplayContent.setImeLayeringTarget(target);
mDisplayContent.updateImeInputAndControlTarget(target);
- mImeProvider.scheduleShowImePostLayout(target);
+ mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
assertTrue(mImeProvider.isReadyToShowIme());
}
@@ -78,11 +78,33 @@
mDisplayContent.setImeLayeringTarget(target);
mDisplayContent.setImeControlTarget(target);
- mImeProvider.scheduleShowImePostLayout(target);
+ mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
assertFalse(mImeProvider.isImeShowing());
mImeProvider.checkShowImePostLayout();
assertTrue(mImeProvider.isImeShowing());
mImeProvider.setImeShowing(false);
assertFalse(mImeProvider.isImeShowing());
}
+
+ @Test
+ public void testSetFrozen() {
+ WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ makeWindowVisibleAndDrawn(ime);
+ mImeProvider.setWindowContainer(ime, null, null);
+ mImeProvider.setServerVisible(true);
+ mImeProvider.setClientVisible(true);
+ mImeProvider.updateVisibility();
+ assertTrue(mImeProvider.getSource().isVisible());
+
+ // Freezing IME states and set the server visible as false.
+ mImeProvider.setFrozen(true);
+ mImeProvider.setServerVisible(false);
+ // Expect the IME insets visible won't be changed.
+ assertTrue(mImeProvider.getSource().isVisible());
+
+ // Unfreeze IME states and expect the IME insets became invisible due to pending IME
+ // visible state updated.
+ mImeProvider.setFrozen(false);
+ assertFalse(mImeProvider.getSource().isVisible());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 1246d1e..1be9de7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -35,6 +35,7 @@
import junit.framework.Assert;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -72,6 +73,7 @@
mLetterboxConfigurationPersister.start();
}
+ @After
public void tearDown() throws InterruptedException {
deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
waitForCompletion(mPersisterQueue);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 9d2eb26..bcaf886 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,22 +16,29 @@
package com.android.server.wm;
+import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.hardware.display.DisplayManager;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.view.Display.Mode;
+import android.view.Surface;
import android.view.WindowManager.LayoutParams;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,7 +52,6 @@
@RunWith(WindowTestRunner.class)
@FlakyTest
public class RefreshRatePolicyTest extends WindowTestsBase {
- private static final float FLOAT_TOLERANCE = 0.01f;
private static final int HI_MODE_ID = 1;
private static final float HI_REFRESH_RATE = 90;
@@ -57,6 +63,19 @@
private RefreshRatePolicy mPolicy;
private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+ private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+ private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+ private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
+ new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
+ new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
// Parcel and Unparcel the LayoutParams in the window state to test the path the object
// travels from the app's process to system server
@@ -89,6 +108,8 @@
WindowState createWindow(String name) {
WindowState window = createWindow(null, TYPE_BASE_APPLICATION, name);
when(window.getDisplayInfo()).thenReturn(mDisplayInfo);
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
return window;
}
@@ -98,20 +119,23 @@
cameraUsingWindow.mAttrs.packageName = "com.android.test";
parcelLayoutParams(cameraUsingWindow);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeRefreshRateRangeForPackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -122,20 +146,23 @@
cameraUsingWindow.mAttrs.packageName = "com.android.test";
parcelLayoutParams(cameraUsingWindow);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, MID_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(MID_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeRefreshRateRangeForPackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -146,20 +173,23 @@
cameraUsingWindow.mAttrs.packageName = "com.android.test";
parcelLayoutParams(cameraUsingWindow);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE - 10, HI_REFRESH_RATE + 10);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(HI_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeRefreshRateRangeForPackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -171,8 +201,8 @@
parcelLayoutParams(denylistedWindow);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(denylistedWindow));
+ assertEquals(FRAME_RATE_VOTE_DENY_LIST, denylistedWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
}
@@ -185,8 +215,8 @@
parcelLayoutParams(overrideWindow);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -199,8 +229,8 @@
parcelLayoutParams(overrideWindow);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -214,8 +244,8 @@
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
@@ -231,8 +261,8 @@
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
@@ -246,8 +276,8 @@
overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
parcelLayoutParams(overrideWindow);
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
@@ -255,7 +285,8 @@
overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -267,8 +298,8 @@
overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(overrideWindow);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
@@ -276,7 +307,8 @@
overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -288,8 +320,8 @@
parcelLayoutParams(window);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
@@ -297,7 +329,8 @@
window.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
@@ -311,7 +344,8 @@
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
@@ -321,7 +355,8 @@
cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -332,7 +367,8 @@
window.mAttrs.preferredMaxDisplayRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(window);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
@@ -340,7 +376,8 @@
window.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
@@ -351,7 +388,8 @@
window.mAttrs.preferredMinDisplayRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(window);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
@@ -359,7 +397,8 @@
window.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
@@ -370,8 +409,92 @@
window.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(window);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
+
+ @Test
+ public void testSwitchingTypeForExactVote() {
+ final WindowState window = createWindow("window");
+ window.mAttrs.preferredDisplayModeId = HI_MODE_ID;
+ parcelLayoutParams(window);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+ }
+
+ @Test
+ public void testSwitchingTypeForPreferredVote() {
+ final WindowState window = createWindow("window");
+ window.mAttrs.preferredRefreshRate = HI_REFRESH_RATE;
+ parcelLayoutParams(window);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+ }
+
+ @Test
+ public void testSwitchingTypeForDenylist() {
+ when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
+
+ final WindowState window = createWindow("window");
+ window.mAttrs.packageName = "com.android.test";
+ parcelLayoutParams(window);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index c906abc..e65610f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -25,6 +25,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
@@ -71,6 +72,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.times;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -87,6 +89,7 @@
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
+import android.view.InsetsSource;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -105,6 +108,9 @@
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
/**
* Tests for Size Compatibility mode.
@@ -2368,6 +2374,48 @@
}
@Test
+ public void testLetterboxDetailsForTaskBar_letterboxNotOverlappingTaskBar() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ final int screenHeight = 2200;
+ final int screenWidth = 1400;
+ final int taskbarHeight = 200;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Move first activity to split screen which takes half of the screen.
+ organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
+ organizer.putTaskToPrimary(mTask, true);
+
+ final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
+
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
+
+ final WindowState w1 = addWindowToActivity(mActivity);
+ w1.mAboveInsetsState.addSource(navSource);
+
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // Refresh the letterboxes
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
+ verify(mTransaction, times(2)).setWindowCrop(
+ eq(w1.getSurfaceControl()),
+ cropCapturer.capture()
+ );
+ final List<Rect> capturedCrops = cropCapturer.getAllValues();
+
+ final int expectedHeight = screenHeight / 2 - taskbarHeight;
+ assertEquals(2, capturedCrops.size());
+ assertEquals(expectedHeight, capturedCrops.get(0).bottom);
+ assertEquals(expectedHeight, capturedCrops.get(1).bottom);
+ }
+
+ @Test
public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(2800, 1000);
@@ -3160,7 +3208,7 @@
/** Asserts that the size of activity is larger than its parent so it is scaling. */
private void assertScaled() {
assertTrue(mActivity.inSizeCompatMode());
- assertNotEquals(1f, mActivity.getSizeCompatScale(), 0.0001f /* delta */);
+ assertNotEquals(1f, mActivity.getCompatScale(), 0.0001f /* delta */);
}
/** Asserts that the activity is best fitted in the parent. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 846a506..5e1fae0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -192,16 +192,17 @@
}
private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
- private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
+ private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
@Override
- public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
- mSyncBufferCallback = syncBufferCallback;
+ public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+ mTransactionReadyCallback = transactionReadyCallback;
}
void onBufferReady() {
SurfaceControl.Transaction t = new StubTransaction();
- mSyncBufferCallback.onBufferReady(t);
+ mTransactionReadyCallback.onTransactionReady(t);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 4202f46..2b49314 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -62,10 +62,12 @@
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -403,7 +405,7 @@
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
- assertEquals(activity.intent, change.getActivityIntent());
+ assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent());
assertNotEquals(activity.token, change.getActivityToken());
mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
assertApplyTransactionAllowed(mTransaction);
@@ -415,14 +417,70 @@
}
@Test
+ public void testOnActivityReparentedToTask_untrustedEmbed_notReported() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
+ DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final Task task = createTask(mDisplayContent);
+ task.addChild(mTaskFragment, POSITION_TOP);
+ final ActivityRecord activity = createActivityRecord(task);
+
+ // Make sure the activity is embedded in untrusted mode.
+ activity.info.applicationInfo.uid = uid + 1;
+ doReturn(pid + 1).when(activity).getPid();
+ task.effectiveUid = uid;
+ doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
+ doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
+ doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+
+ // Notify organizer if it was embedded before entered Pip.
+ // Create a temporary token since the activity doesn't belong to the same process.
+ clearInvocations(mOrganizer);
+ activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+ mController.onActivityReparentedToTask(activity);
+ mController.dispatchPendingEvents();
+
+ // Disallow organizer to reparent activity that is untrusted embedded.
+ verify(mOrganizer, never()).onTransactionReady(mTransactionCaptor.capture());
+ }
+
+ @Test
+ public void testOnActivityReparentedToTask_trimReportedIntent() {
+ // Make sure the activity pid/uid is the same as the organizer caller.
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final ActivityRecord activity = createActivityRecord(mDisplayContent);
+ final Task task = activity.getTask();
+ activity.info.applicationInfo.uid = uid;
+ doReturn(pid).when(activity).getPid();
+ task.effectiveUid = uid;
+ activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+
+ // Test the Intent trim in #assertIntentTrimmed
+ activity.intent.setComponent(new ComponentName("TestPackage", "TestClass"))
+ .setPackage("TestPackage")
+ .setAction("TestAction")
+ .setData(mock(Uri.class))
+ .putExtra("Test", 123)
+ .setFlags(10);
+
+ mController.onActivityReparentedToTask(activity);
+ mController.dispatchPendingEvents();
+
+ assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
+ }
+
+ @Test
public void testRegisterRemoteAnimations() {
- mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
+ mController.registerRemoteAnimations(mIOrganizer, mDefinition);
- assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+ assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
- mController.unregisterRemoteAnimations(mIOrganizer, TASK_ID);
+ mController.unregisterRemoteAnimations(mIOrganizer);
- assertNull(mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+ assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
}
@Test
@@ -764,6 +822,21 @@
}
@Test
+ public void testOnTransactionHandled_skipTransactionForUnregisterOrganizer() {
+ mController.unregisterOrganizer(mIOrganizer);
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+ final IBinder fragmentToken = new Binder();
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ mController.onTransactionHandled(new Binder(), mTransaction,
+ getTransitionType(mTransaction), false /* shouldApplyIndependently */);
+
+ // Nothing should happen as the organizer is not registered.
+ assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ @Test
public void testOrganizerRemovedWithPendingEvents() {
final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
.setCreateParentTask()
@@ -1425,7 +1498,8 @@
final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(taskId, change.getTaskId());
- assertEquals(intent, change.getActivityIntent());
+ assertIntentsEqualForOrganizer(intent, change.getActivityIntent());
+ assertIntentTrimmed(change.getActivityIntent());
assertEquals(activityToken, change.getActivityToken());
}
@@ -1452,4 +1526,17 @@
mockParent.lastActiveTime = 100;
doReturn(true).when(mockParent).shouldBeVisible(any());
}
+
+ private static void assertIntentsEqualForOrganizer(@NonNull Intent expected,
+ @NonNull Intent actual) {
+ assertEquals(expected.getComponent(), actual.getComponent());
+ assertEquals(expected.getPackage(), actual.getPackage());
+ assertEquals(expected.getAction(), actual.getAction());
+ }
+
+ private static void assertIntentTrimmed(@NonNull Intent intent) {
+ assertNull(intent.getData());
+ assertNull(intent.getExtras());
+ assertEquals(0, intent.getFlags());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 83f1789..3ff2c0e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -118,10 +118,13 @@
doReturn(true).when(mTaskFragment).isVisibleRequested();
clearInvocations(mTransaction);
+ mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
mTaskFragment.setBounds(endBounds);
+ assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
+ mTaskFragment.initializeChangeTransition(startBounds);
+ mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
// Surface reset when prepare transition.
- verify(mTaskFragment).initializeChangeTransition(startBounds);
verify(mTransaction).setPosition(mLeash, 0, 0);
verify(mTransaction).setWindowCrop(mLeash, 0, 0);
@@ -166,7 +169,7 @@
mTaskFragment.setBounds(endBounds);
- verify(mTaskFragment, never()).initializeChangeTransition(any());
+ assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index aaf855f..2189ef8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -639,6 +639,29 @@
}
@Test
+ public void testBoundsInOptionsInfersFullscreenWithBoundsOnFreeformSupportFullscreenDisplay() {
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FULLSCREEN);
+ mAtm.mTaskSupervisor.mService.mSupportsFreeformWindowManagement = true;
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ final Rect expectedBounds = new Rect(0, 0, 100, 100);
+ options.setLaunchBounds(expectedBounds);
+
+ mCurrent.mPreferredTaskDisplayArea = fullscreenDisplay.getDefaultTaskDisplayArea();
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setOptions(options).calculate());
+
+ // Setting bounds shouldn't lead to freeform windowing mode on fullscreen display by
+ // default (even with freeform support), but we need to check here if the bounds is set even
+ // with fullscreen windowing mode in case it's restored later.
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ assertEquals(expectedBounds, mResult.mBounds);
+ }
+
+ @Test
public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
@@ -1020,6 +1043,8 @@
WINDOWING_MODE_FULLSCREEN);
final ActivityRecord source = createSourceActivity(fullscreenDisplay);
source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
+ // Set some bounds to avoid conflict with the other activity.
+ source.setBounds(100, 100, 200, 200);
final ActivityOptions options = ActivityOptions.makeBasic();
final Rect expected = new Rect(0, 0, 150, 150);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 66bf78b..d52c34b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -36,6 +36,7 @@
import static android.view.Surface.ROTATION_90;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -80,6 +81,7 @@
import android.util.Xml;
import android.view.Display;
import android.view.DisplayInfo;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.MediumTest;
@@ -433,6 +435,24 @@
}
@Test
+ public void testPropagateFocusedStateToRootTask() {
+ final Task rootTask = createTask(mDefaultDisplay);
+ final Task leafTask = createTaskInRootTask(rootTask, 0 /* userId */);
+
+ final ActivityRecord activity = createActivityRecord(leafTask);
+
+ leafTask.getDisplayContent().setFocusedApp(activity);
+
+ assertTrue(leafTask.getTaskInfo().isFocused);
+ assertTrue(rootTask.getTaskInfo().isFocused);
+
+ leafTask.getDisplayContent().setFocusedApp(null);
+
+ assertFalse(leafTask.getTaskInfo().isFocused);
+ assertFalse(rootTask.getTaskInfo().isFocused);
+ }
+
+ @Test
public void testReturnsToHomeRootTask() throws Exception {
final Task task = createTask(1);
spyOn(task);
@@ -1471,6 +1491,26 @@
tf0, parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
}
+ @Test
+ public void testReorderActivityToFront() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ doNothing().when(task).onActivityVisibleRequestedChanged();
+ final ActivityRecord activity = task.getTopMostActivity();
+
+ final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = fragment.getTopMostActivity();
+ task.moveActivityToFront(activity);
+ assertEquals("Activity must be moved to front", activity, task.getTopMostActivity());
+
+ doNothing().when(fragment).sendTaskFragmentInfoChanged();
+ task.moveActivityToFront(embeddedActivity);
+ assertEquals("Activity must be moved to front", embeddedActivity,
+ task.getTopMostActivity());
+ assertEquals("Activity must not be embedded", embeddedActivity,
+ task.getTopChild());
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index bb5aceb..6e72bf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.Nullable;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -26,6 +27,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.ScrollCaptureResponse;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -117,10 +119,12 @@
}
@Override
- public void showInsets(int types, boolean fromIme) throws RemoteException {
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+ throws RemoteException {
}
@Override
- public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+ throws RemoteException {
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 3b64c51..690c2aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1176,7 +1176,7 @@
assertTrue(rootTask2.isOrganized());
// Verify a back pressed does not call the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1187,7 +1187,7 @@
rootTask.mRemoteToken.toWindowContainerToken(), true);
// Verify now that the back press does call the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1198,7 +1198,7 @@
rootTask.mRemoteToken.toWindowContainerToken(), false);
// Verify now that the back press no longer calls the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1404,7 +1404,7 @@
mWm.mWindowPlacerLocked.deferLayout();
rootTask.removeImmediately();
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(record.token,
new IRequestFinishCallback.Default());
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0139f6a..1b79dd3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -26,6 +26,7 @@
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -95,6 +96,8 @@
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
@@ -800,6 +803,39 @@
}
@Test
+ public void testEmbeddedActivityResizing_clearAllDrawn() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+ final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
+ "App window");
+ doReturn(true).when(embeddedActivity).isVisible();
+ embeddedActivity.mVisibleRequested = true;
+ makeWindowVisible(win);
+ win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
+ // Set the bounds twice:
+ // 1. To make sure there is no orientation change after #reportResized, which can also cause
+ // #clearAllDrawn.
+ // 2. Make #isLastConfigReportedToClient to be false after #reportResized, so it can process
+ // to check if we need redraw.
+ embeddedTf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ embeddedTf.setBounds(0, 0, 1000, 2000);
+ win.reportResized();
+ embeddedTf.setBounds(500, 0, 1000, 2000);
+
+ // Clear all drawn when the embedded TaskFragment is in mDisplayContent.mChangingContainers.
+ win.updateResizingWindowIfNeeded();
+ verify(embeddedActivity, never()).clearAllDrawn();
+
+ mDisplayContent.mChangingContainers.add(embeddedTf);
+ win.updateResizingWindowIfNeeded();
+ verify(embeddedActivity).clearAllDrawn();
+ }
+
+ @Test
public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
win0.mActivityRecord.mVisibleRequested = false;
@@ -999,8 +1035,9 @@
// Simulate app requests IME with updating all windows Insets State when IME is above app.
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
+ app.setRequestedVisibleTypes(ime(), ime());
assertTrue(mDisplayContent.shouldImeAttachedToApp());
- controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+ controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
controller.getImeSourceProvider().getSource().setVisible(true);
controller.updateAboveInsetsState(false);
@@ -1036,8 +1073,9 @@
app2.mActivityRecord.mImeInsetsFrozenUntilStartInput = true;
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
+ app.setRequestedVisibleTypes(ime(), ime());
assertTrue(mDisplayContent.shouldImeAttachedToApp());
- controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+ controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
controller.getImeSourceProvider().getSource().setVisible(true);
controller.updateAboveInsetsState(false);
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 eca7cbb..ab042d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -100,6 +100,7 @@
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
import android.window.ITransitionPlayer;
import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
@@ -848,11 +849,13 @@
}
@Override
- public void showInsets(int i, boolean b) throws RemoteException {
+ public void showInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+ throws RemoteException {
}
@Override
- public void hideInsets(int i, boolean b) throws RemoteException {
+ public void hideInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+ throws RemoteException {
}
@Override
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7f5beb1..4fd2b78 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2392,6 +2392,8 @@
@Override
public void setAppStandbyBucket(String packageName, int bucket, int userId) {
+ super.setAppStandbyBucket_enforcePermission();
+
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
@@ -2442,6 +2444,8 @@
@Override
public void setAppStandbyBuckets(ParceledListSlice appBuckets, int userId) {
+ super.setAppStandbyBuckets_enforcePermission();
+
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
@@ -2493,6 +2497,8 @@
public void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime,
int userId) {
+ super.setEstimatedLaunchTime_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
@@ -2506,6 +2512,8 @@
@Override
public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) {
+ super.setEstimatedLaunchTimes_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 86f877f..72f6cc3 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -504,6 +504,15 @@
}
@Override
+ public boolean hasDevicePermissionWithIdentity(UsbDevice device, String packageName,
+ int pid, int uid) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ final int userId = UserHandle.getUserId(uid);
+ return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid);
+ }
+
+ @Override
public boolean hasAccessoryPermission(UsbAccessory accessory) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -518,6 +527,14 @@
}
@Override
+ public boolean hasAccessoryPermissionWithIdentity(UsbAccessory accessory, int pid, int uid) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ final int userId = UserHandle.getUserId(uid);
+ return getPermissionsForUser(userId).hasPermission(accessory, pid, uid);
+ }
+
+ @Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
index df63795..7a41b50 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
@@ -46,4 +46,4 @@
// TODO Add reporting specific to this descriptor
super.report(canvas);
}
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
index 4aa8ca2..32275a6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
@@ -46,4 +46,4 @@
super.report(canvas);
// TODO Add reporting specific to this descriptor
}
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
index 5ce842e..0692066 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
@@ -47,4 +47,4 @@
super.report(canvas);
// TODO Add reporting specific to this descriptor
}
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
index 8e9b0d8..604dd66 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
@@ -47,4 +47,4 @@
super.report(canvas);
// TODO Add reporting specific to this descriptor
}
-};
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
new file mode 100644
index 0000000..d5eea1f
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
@@ -0,0 +1,233 @@
+/*
+ * 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.server.voiceinteraction;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+
+import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.media.permission.Identity;
+import android.os.ParcelFileDescriptor;
+import android.service.voice.HotwordAudioStream;
+import android.service.voice.HotwordDetectedResult;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+final class HotwordAudioStreamManager {
+
+ private static final String TAG = "HotwordAudioStreamManager";
+ private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService";
+ private static final String TASK_ID_PREFIX = "HotwordDetectedResult@";
+ private static final String THREAD_NAME_PREFIX = "Copy-";
+
+ private final AppOpsManager mAppOpsManager;
+ private final Identity mVoiceInteractorIdentity;
+ private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+ HotwordAudioStreamManager(@NonNull AppOpsManager appOpsManager,
+ @NonNull Identity voiceInteractorIdentity) {
+ mAppOpsManager = appOpsManager;
+ mVoiceInteractorIdentity = voiceInteractorIdentity;
+ }
+
+ /**
+ * Starts copying the audio streams in the given {@link HotwordDetectedResult}.
+ * <p>
+ * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
+ * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()}
+ * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamManager}. The
+ * returned value should be passed on to the client (i.e., the voice interactor).
+ * </p>
+ *
+ * @throws IOException If there was an error creating the managed pipe.
+ */
+ @NonNull
+ public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
+ throws IOException {
+ List<HotwordAudioStream> audioStreams = result.getAudioStreams();
+ if (audioStreams.isEmpty()) {
+ return result;
+ }
+
+ List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size());
+ List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks = new ArrayList<>(
+ audioStreams.size());
+ for (HotwordAudioStream audioStream : audioStreams) {
+ ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe();
+ ParcelFileDescriptor clientAudioSource = clientPipe[0];
+ ParcelFileDescriptor clientAudioSink = clientPipe[1];
+ HotwordAudioStream newAudioStream =
+ audioStream.buildUpon().setAudioStreamParcelFileDescriptor(
+ clientAudioSource).build();
+ newAudioStreams.add(newAudioStream);
+
+ ParcelFileDescriptor serviceAudioSource =
+ audioStream.getAudioStreamParcelFileDescriptor();
+ sourcesAndSinks.add(new Pair<>(serviceAudioSource, clientAudioSink));
+ }
+
+ String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result);
+ mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, sourcesAndSinks));
+
+ return result.buildUpon().setAudioStreams(newAudioStreams).build();
+ }
+
+ private class HotwordDetectedResultCopyTask implements Runnable {
+ private final String mResultTaskId;
+ private final List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> mSourcesAndSinks;
+ private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+ HotwordDetectedResultCopyTask(String resultTaskId,
+ List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks) {
+ mResultTaskId = resultTaskId;
+ mSourcesAndSinks = sourcesAndSinks;
+ }
+
+ @Override
+ public void run() {
+ Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId);
+ int size = mSourcesAndSinks.size();
+ List<SingleAudioStreamCopyTask> tasks = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink =
+ mSourcesAndSinks.get(i);
+ ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
+ ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
+ String streamTaskId = mResultTaskId + "@" + i;
+ tasks.add(new SingleAudioStreamCopyTask(streamTaskId, serviceAudioSource,
+ clientAudioSink));
+ }
+
+ if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag, OP_MESSAGE) == MODE_ALLOWED) {
+ try {
+ // TODO(b/244599891): Set timeout, close after inactivity
+ mExecutorService.invokeAll(tasks);
+ } catch (InterruptedException e) {
+ Slog.e(TAG, mResultTaskId + ": Task was interrupted", e);
+ bestEffortPropagateError(e.getMessage());
+ } finally {
+ mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag);
+ }
+ } else {
+ bestEffortPropagateError(
+ "Failed to obtain RECORD_AUDIO_HOTWORD permission for "
+ + SoundTriggerSessionPermissionsDecorator.toString(
+ mVoiceInteractorIdentity));
+ }
+ }
+
+ private void bestEffortPropagateError(@NonNull String errorMessage) {
+ try {
+ for (Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink :
+ mSourcesAndSinks) {
+ ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
+ ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
+ serviceAudioSource.closeWithError(errorMessage);
+ clientAudioSink.closeWithError(errorMessage);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e);
+ }
+ }
+ }
+
+ private static class SingleAudioStreamCopyTask implements Callable<Void> {
+ // TODO: Make this buffer size customizable from updateState()
+ private static final int COPY_BUFFER_LENGTH = 2_560;
+
+ private final String mStreamTaskId;
+ private final ParcelFileDescriptor mAudioSource;
+ private final ParcelFileDescriptor mAudioSink;
+
+ SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource,
+ ParcelFileDescriptor audioSink) {
+ mStreamTaskId = streamTaskId;
+ mAudioSource = audioSource;
+ mAudioSink = audioSink;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ Thread.currentThread().setName(THREAD_NAME_PREFIX + mStreamTaskId);
+
+ // Note: We are intentionally NOT using try-with-resources here. If we did,
+ // the ParcelFileDescriptors will be automatically closed WITHOUT errors before we go
+ // into the IOException-catch block. We want to propagate the error while closing the
+ // PFDs.
+ InputStream fis = null;
+ OutputStream fos = null;
+ try {
+ fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource);
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink);
+ byte[] buffer = new byte[COPY_BUFFER_LENGTH];
+ while (true) {
+ if (Thread.interrupted()) {
+ Slog.e(TAG,
+ mStreamTaskId + ": SingleAudioStreamCopyTask task was interrupted");
+ break;
+ }
+
+ int bytesRead = fis.read(buffer);
+ if (bytesRead < 0) {
+ Slog.i(TAG, mStreamTaskId + ": Reached end of audio stream");
+ break;
+ }
+ if (bytesRead > 0) {
+ if (DEBUG) {
+ // TODO(b/244599440): Add proper logging
+ Slog.d(TAG, mStreamTaskId + ": Copied " + bytesRead
+ + " bytes from audio stream. First 20 bytes=" + Arrays.toString(
+ Arrays.copyOfRange(buffer, 0, 20)));
+ }
+ fos.write(buffer, 0, bytesRead);
+ }
+ // TODO(b/244599891): Close PFDs after inactivity
+ }
+ } catch (IOException e) {
+ mAudioSource.closeWithError(e.getMessage());
+ mAudioSink.closeWithError(e.getMessage());
+ Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ if (fos != null) {
+ fos.close();
+ }
+ }
+
+ return null;
+ }
+ }
+
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 372fdaf..3e49aed 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -19,7 +19,6 @@
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -35,7 +34,12 @@
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
@@ -144,6 +148,7 @@
private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
private static final int CALLBACK_DETECT_TIMEOUT = -3;
+ private static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
// Hotword metrics
private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
@@ -170,11 +175,15 @@
HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
private static final int METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+ private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION =
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
// TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor();
+ private final AppOpsManager mAppOpsManager;
+ private final HotwordAudioStreamManager mHotwordAudioStreamManager;
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@@ -196,7 +205,7 @@
@Nullable AttentionManagerInternal mAttentionManagerInternal = null;
final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
- this::setProximityMeters;
+ this::setProximityValue;
volatile HotwordDetectionServiceIdentity mIdentity;
@@ -235,6 +244,9 @@
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager,
+ mVoiceInteractorIdentity);
mDetectionComponentName = serviceName;
mUser = userId;
mCallback = callback;
@@ -336,11 +348,10 @@
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
initResultMetricsResult);
} catch (RemoteException e) {
- // TODO: Add a new atom for RemoteException case, the error doesn't very
- // correct here
Slog.w(TAG, "Failed to report initialization status: " + e);
- HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
- METRICS_INIT_CALLBACK_STATE_ERROR);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
}
};
@@ -352,6 +363,9 @@
} catch (RemoteException e) {
// TODO: (b/181842909) Report an error to voice interactor
Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}).whenComplete((res, err) -> {
@@ -366,8 +380,9 @@
METRICS_INIT_UNKNOWN_TIMEOUT);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
- HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
- METRICS_INIT_CALLBACK_STATE_ERROR);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
} else if (err != null) {
Slog.w(TAG, "Failed to update state: " + err);
@@ -488,14 +503,20 @@
mSoftwareCallback.onError();
return;
}
- saveProximityMetersToBundle(result);
- mSoftwareCallback.onDetected(result, null, null);
- if (result != null) {
- Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
- + " bits from hotword trusted process");
- if (mDebugHotwordLogging) {
- Slog.i(TAG, "Egressed detected result: " + result);
- }
+ saveProximityValueToBundle(result);
+ HotwordDetectedResult newResult;
+ try {
+ newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+ } catch (IOException e) {
+ // TODO: Write event
+ mSoftwareCallback.onError();
+ return;
+ }
+ mSoftwareCallback.onDetected(newResult, null, null);
+ Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+ + " bits from hotword trusted process");
+ if (mDebugHotwordLogging) {
+ Slog.i(TAG, "Egressed detected result: " + newResult);
}
}
}
@@ -610,20 +631,27 @@
enforcePermissionsForDataDelivery();
enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
} catch (SecurityException e) {
+ Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
return;
}
- saveProximityMetersToBundle(result);
- externalCallback.onKeyphraseDetected(recognitionEvent, result);
- if (result != null) {
- Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
- + " bits from hotword trusted process");
- if (mDebugHotwordLogging) {
- Slog.i(TAG, "Egressed detected result: " + result);
- }
+ saveProximityValueToBundle(result);
+ HotwordDetectedResult newResult;
+ try {
+ newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+ } catch (IOException e) {
+ // TODO: Write event
+ externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
+ return;
+ }
+ externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+ Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+ + " bits from hotword trusted process");
+ if (mDebugHotwordLogging) {
+ Slog.i(TAG, "Egressed detected result: " + newResult);
}
}
}
@@ -678,6 +706,9 @@
externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report onError status: ", e);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
},
MAX_VALIDATION_TIMEOUT_MILLIS,
@@ -722,6 +753,7 @@
}
private void restartProcessLocked() {
+ // TODO(b/244598068): Check HotwordAudioStreamManager first
Slog.v(TAG, "Restarting hotword detection process");
ServiceConnection oldConnection = mRemoteHotwordDetectionService;
HotwordDetectionServiceIdentity previousIdentity = mIdentity;
@@ -737,6 +769,9 @@
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call #rejected");
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
mValidatingDspTrigger = false;
}
@@ -752,6 +787,9 @@
mCallback.onProcessRestarted();
} catch (RemoteException e) {
Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
// Restart listening from microphone if the hotword process has been restarted.
@@ -891,6 +929,9 @@
callback.onError();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to report onError status: " + ex);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
} finally {
synchronized (mLock) {
@@ -956,16 +997,24 @@
callback.onError();
return;
}
- callback.onDetected(triggerResult, null /* audioFormat */,
+ HotwordDetectedResult newResult;
+ try {
+ newResult =
+ mHotwordAudioStreamManager.startCopyingAudioStreams(
+ triggerResult);
+ } catch (IOException e) {
+ // TODO: Write event
+ callback.onError();
+ return;
+ }
+ callback.onDetected(newResult, null /* audioFormat */,
null /* audioStream */);
- if (triggerResult != null) {
- Slog.i(TAG, "Egressed "
- + HotwordDetectedResult.getUsageSize(triggerResult)
- + " bits from hotword trusted process");
- if (mDebugHotwordLogging) {
- Slog.i(TAG,
- "Egressed detected result: " + triggerResult);
- }
+ Slog.i(TAG, "Egressed "
+ + HotwordDetectedResult.getUsageSize(newResult)
+ + " bits from hotword trusted process");
+ if (mDebugHotwordLogging) {
+ Slog.i(TAG,
+ "Egressed detected result: " + newResult);
}
}
});
@@ -1070,6 +1119,9 @@
mCallback.onError(HOTWORD_DETECTION_SERVICE_DIED);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report onError status: " + e);
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
}
}
}
@@ -1189,15 +1241,15 @@
});
}
- private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+ private void saveProximityValueToBundle(HotwordDetectedResult result) {
synchronized (mLock) {
if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
- result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+ result.setProximity(mProximityMeters);
}
}
}
- private void setProximityMeters(double proximityMeters) {
+ private void setProximityValue(double proximityMeters) {
synchronized (mLock) {
mProximityMeters = proximityMeters;
}
@@ -1224,7 +1276,7 @@
Binder.withCleanCallingIdentity(() -> {
enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
- mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp,
+ mAppOpsManager.noteOpNoThrow(hotwordOp,
mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
@@ -1269,4 +1321,4 @@
private static final String OP_MESSAGE =
"Providing hotword detection result to VoiceInteractionService";
-};
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d1901d..0a660b0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -639,7 +639,7 @@
private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
if (callback != null) {
- HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
+ HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
voiceInteractionServiceUid);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index f8bc499..763024f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -913,4 +913,4 @@
}
}
};
-};
+}
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index eb0c432..dcaf858 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -4,3 +4,7 @@
tgunn@google.com
xiaotonj@google.com
rgreenwalt@google.com
+chinmayd@google.com
+grantmenke@google.com
+pmadapurmath@google.com
+tjstuart@google.com
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 432af3a..5cef2cb 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -168,6 +168,18 @@
public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
/**
+ * Extra key intended for {@link InCallService}s that notify the user of an incoming call. When
+ * EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB returns true, the {@link InCallService} should not
+ * interrupt the user of the incoming call because the call is being suppressed by Do Not
+ * Disturb settings.
+ *
+ * This extra will be removed from the {@link Call} object for {@link InCallService}s that do
+ * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+ */
+ public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB =
+ "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
+
+ /**
* Key for extra used to pass along a list of {@link PhoneAccountSuggestion}s to the in-call
* UI when a call enters the {@link #STATE_SELECT_PHONE_ACCOUNT} state. The list included here
* will have the same length and be in the same order as the list passed with
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index ff87ab0..a69dfb0b 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -111,6 +111,8 @@
public static final int FILTERING_INITIATED = 106;
public static final int FILTERING_COMPLETED = 107;
public static final int FILTERING_TIMED_OUT = 108;
+ public static final int DND_CHECK_INITIATED = 109;
+ public static final int DND_CHECK_COMPLETED = 110;
public static final int SKIP_RINGING = 200;
public static final int SILENCE = 201;
@@ -195,6 +197,7 @@
public static final int BLOCK_CHECK_FINISHED_TIMING = 9;
public static final int FILTERING_COMPLETED_TIMING = 10;
public static final int FILTERING_TIMED_OUT_TIMING = 11;
+ public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12;
/** {@hide} */
public static final int START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING = 12;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index ccec67e..af37ed5 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1851,7 +1851,7 @@
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getCallStateUsingPackage(mContext.getPackageName(),
+ return service.getCallStateUsingPackage(mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (RemoteException e) {
Log.d(TAG, "RemoteException calling getCallState().", e);
diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
index 8b01cb3..2787d83 100644
--- a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
@@ -199,7 +199,6 @@
*/
@Override
public Object clone() throws CloneNotSupportedException {
- super.clone();
int len = mData.length;
byte[] dstBytes = new byte[len];
System.arraycopy(mData, 0, dstBytes, 0, len);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 453c656..ed96a9b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7095,6 +7095,274 @@
public static final String KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT =
KEY_PREFIX + "refresh_geolocation_timeout_millis_int";
+ /**
+ * List of 3GPP access network technologies where e911 over IMS is supported
+ * in the home network and domestic 3rd-party networks. The order in the list represents
+ * the preference. The domain selection service shall scan the network type in the order
+ * of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#NGRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#EUTRAN}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
+ * @hide
+ */
+ public static final String
+ KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ + "emergency_over_ims_supported_3gpp_network_types_int_array";
+
+ /**
+ * List of 3GPP access network technologies where e911 over IMS is supported
+ * in the roaming network and non-domestic 3rd-party networks. The order in the list
+ * represents the preference. The domain selection service shall scan the network type
+ * in the order of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#NGRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#EUTRAN}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
+ * @hide
+ */
+ public static final String
+ KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ + "emergency_over_ims_roaming_supported_3gpp_network_types_int_array";
+
+ /**
+ * List of CS access network technologies where circuit-switched emergency calls are
+ * supported in the home network and domestic 3rd-party networks. The order in the list
+ * represents the preference. The domain selection service shall scan the network type
+ * in the order of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#CDMA2000}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY =
+ KEY_PREFIX + "emergency_over_cs_supported_access_network_types_int_array";
+
+ /**
+ * List of CS access network technologies where circuit-switched emergency calls are
+ * supported in the roaming network and non-domestic 3rd-party networks. The order
+ * in the list represents the preference. The domain selection service shall scan
+ * the network type in the order of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#CDMA2000}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
+ * @hide
+ */
+ public static final String
+ KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ + "emergency_over_cs_roaming_supported_access_network_types_int_array";
+
+ /** @hide */
+ @IntDef({
+ DOMAIN_CS,
+ DOMAIN_PS_3GPP,
+ DOMAIN_PS_NON_3GPP
+ })
+ public @interface EmergencyDomain {}
+
+ /**
+ * Circuit switched domain.
+ * @hide
+ */
+ public static final int DOMAIN_CS = 1;
+
+ /**
+ * Packet switched domain over 3GPP networks.
+ * @hide
+ */
+ public static final int DOMAIN_PS_3GPP = 2;
+
+ /**
+ * Packet switched domain over non-3GPP networks such as Wi-Fi.
+ * @hide
+ */
+ public static final int DOMAIN_PS_NON_3GPP = 3;
+
+ /**
+ * Specifies the emergency call domain preference for the home network.
+ * The domain selection service shall choose the domain in the order
+ * for attempting the emergency call
+ *
+ * <p>Possible values are,
+ * {@link #DOMAIN_CS}
+ * {@link #DOMAIN_PS_3GPP}
+ * {@link #DOMAIN_PS_NON_3GPP}.
+ *
+ * The default value for this key is
+ * {{@link #DOMAIN_PS_3GPP},
+ * {@link #DOMAIN_CS},
+ * {@link #DOMAIN_PS_NON_3GPP}}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY =
+ KEY_PREFIX + "emergency_domain_preference_int_array";
+
+ /**
+ * Specifies the emergency call domain preference for the roaming network.
+ * The domain selection service shall choose the domain in the order
+ * for attempting the emergency call.
+ *
+ * <p>Possible values are,
+ * {@link #DOMAIN_CS}
+ * {@link #DOMAIN_PS_3GPP}
+ * {@link #DOMAIN_PS_NON_3GPP}.
+ *
+ * The default value for this key is
+ * {{@link #DOMAIN_PS_3GPP},
+ * {@link #DOMAIN_CS},
+ * {@link #DOMAIN_PS_NON_3GPP}}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY =
+ KEY_PREFIX + "emergency_domain_preference_roaming_int_array";
+
+ /**
+ * Specifies if emergency call shall be attempted on IMS, if PS is attached even though IMS
+ * is not registered and normal calls fallback to the CS networks.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
+ KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
+
+ /**
+ * Specifies maximum number of emergency call retries over Wi-Fi.
+ * This is valid only when {@link #DOMAIN_PS_NON_3GPP} is included in
+ * {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY} or
+ * {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY}.
+ *
+ * The default value for this key is 1.
+ * @hide
+ */
+ public static final String KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT =
+ KEY_PREFIX + "maximum_number_of_emergency_tries_over_vowifi_int";
+
+ /**
+ * Emergency scan timer to wait for scan results from radio before attempting the call
+ * over Wi-Fi. On timer expiry, if emergency call on Wi-Fi is allowed and possible,
+ * telephony shall cancel the scan and place the call on Wi-Fi. If emergency call on Wi-Fi
+ * is not possible, then domain seleciton continues to wait for the scan result from the
+ * radio. If an emergency scan result is received before the timer expires, the timer shall
+ * be stopped and no dialing over Wi-Fi will be tried. If this value is set to 0, then
+ * the timer is never started and domain selection waits for the scan result from the radio.
+ *
+ * The default value for the timer is 10 seconds.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT =
+ KEY_PREFIX + "emergency_scan_timer_sec_int";
+
+ /** @hide */
+ @IntDef(prefix = "SCAN_TYPE_",
+ value = {
+ SCAN_TYPE_NO_PREFERENCE,
+ SCAN_TYPE_FULL_SERVICE,
+ SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE})
+ public @interface EmergencyScanType {}
+
+ /**
+ * No specific preference given to the modem. Modem can return an emergency
+ * capable network either with limited service or full service.
+ * @hide
+ */
+ public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+ /**
+ * Modem will attempt to camp on a network with full service only.
+ * @hide
+ */
+ public static final int SCAN_TYPE_FULL_SERVICE = 1;
+
+ /**
+ * Telephony shall attempt full service scan first.
+ * If a full service network is not found, telephony shall attempt a limited service scan.
+ * @hide
+ */
+ public static final int SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE = 2;
+
+ /**
+ * Specifies the preferred emergency network scanning type.
+ *
+ * <p>Possible values are,
+ * {@link #SCAN_TYPE_NO_PREFERENCE}
+ * {@link #SCAN_TYPE_FULL_SERVICE}
+ * {@link #SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE}
+ *
+ * The default value for this key is {@link #SCAN_TYPE_NO_PREFERENCE}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT =
+ KEY_PREFIX + "emergency_network_scan_type_int";
+
+ /**
+ * Specifies the time by which a call should be set up on the current network
+ * once the call is routed on the network. If the call cannot be set up by timer expiry,
+ * call shall be re-dialed on the next available network.
+ * If this value is set to 0, the timer shall be disabled.
+ *
+ * The default value for this key is 0.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT =
+ KEY_PREFIX + "emergency_call_setup_timer_on_current_network_sec_int";
+
+ /**
+ * Specifies if emergency call shall be attempted on IMS only when IMS is registered.
+ * This is applicable only for the case PS is in service.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL =
+ KEY_PREFIX + "emergency_requires_ims_registration_bool";
+
+ /**
+ * Specifies if LTE is preferred when re-scanning networks after the failure of dialing
+ * over NR. If not, CS will be preferred.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL =
+ KEY_PREFIX + "emergency_lte_preferred_after_nr_failed_bool";
+
+ /**
+ * Specifies the numbers to be dialed over CDMA network in case of dialing over CS network.
+ *
+ * The default value for this key is an empty string array.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY =
+ KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
+
+ /**
+ * Specifies if emergency call shall be attempted on IMS only when VoLTE is enabled.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
+ KEY_PREFIX + "emergency_requires_volte_enabled_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7111,6 +7379,56 @@
defaults.putInt(KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT, 10000);
defaults.putInt(KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT, 5000);
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.EUTRAN,
+ });
+
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.EUTRAN,
+ });
+
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.UTRAN,
+ AccessNetworkType.GERAN,
+ });
+
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.UTRAN,
+ AccessNetworkType.GERAN,
+ });
+
+ defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY,
+ new int[] {
+ DOMAIN_PS_3GPP,
+ DOMAIN_CS,
+ DOMAIN_PS_NON_3GPP
+ });
+ defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY,
+ new int[] {
+ DOMAIN_PS_3GPP,
+ DOMAIN_CS,
+ DOMAIN_PS_NON_3GPP
+ });
+
+ defaults.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, false);
+ defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);
+ defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);
+ defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);
+ defaults.putInt(KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT, 0);
+ defaults.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, false);
+ defaults.putBoolean(KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL, false);
+ defaults.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, false);
+ defaults.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
+ new String[] {});
+
return defaults;
}
}
@@ -7920,7 +8238,8 @@
KEY_XCAP_OVER_UT_SUPPORTED_RATS_INT_ARRAY,
new int[] {
AccessNetworkType.EUTRAN,
- AccessNetworkType.IWLAN
+ AccessNetworkType.IWLAN,
+ AccessNetworkType.NGRAN
});
defaults.putString(KEY_UT_AS_SERVER_FQDN_STRING, "");
defaults.putBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL, true);
@@ -8615,7 +8934,12 @@
*
* Used to trade privacy/security against potentially reduced carrier coverage for some
* carriers.
+ *
+ * @deprecated Future versions of Android will disallow carriers from hiding this toggle
+ * because disabling 2g is a security feature that users should always have access to at
+ * their discretion.
*/
+ @Deprecated
public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
/**
@@ -8693,6 +9017,15 @@
public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
/**
+ * Boolean indicating the default VoNR user preference setting.
+ * If true, the VoNR setting will be enabled. If false, it will be disabled initially.
+ *
+ * Enabled by default.
+ *
+ */
+ public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
+
+ /**
* Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
*
* @hide
@@ -8780,7 +9113,7 @@
* The default value is 30 minutes.
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR
- * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED
*/
public static final String
KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -9515,6 +9848,7 @@
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
+ sDefaults.putBoolean(KEY_VONR_ON_BY_DEFAULT_BOOL, true);
sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[] {});
sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG,
TimeUnit.MINUTES.toMillis(30));
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 06cfd67..6e3cfac 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -107,7 +107,7 @@
if ((mMccStr != null && mMncStr == null) || (mMccStr == null && mMncStr != null)) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e257ae06-ac0a-44c0-ba63-823b9f07b3e4"),
"CellIdentity Missing Half of PLMN ID");
}
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 23835a7..b83b400 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1620,29 +1620,26 @@
// If we are not able to find the configuration from carrier config, use the default
// ones.
if (permanentFailureSet == null) {
- permanentFailureSet = new HashSet<Integer>() {
- {
- add(OPERATOR_BARRED);
- add(MISSING_UNKNOWN_APN);
- add(UNKNOWN_PDP_ADDRESS_TYPE);
- add(USER_AUTHENTICATION);
- add(ACTIVATION_REJECT_GGSN);
- add(SERVICE_OPTION_NOT_SUPPORTED);
- add(SERVICE_OPTION_NOT_SUBSCRIBED);
- add(NSAPI_IN_USE);
- add(ONLY_IPV4_ALLOWED);
- add(ONLY_IPV6_ALLOWED);
- add(PROTOCOL_ERRORS);
- add(RADIO_POWER_OFF);
- add(TETHERED_CALL_ACTIVE);
- add(RADIO_NOT_AVAILABLE);
- add(UNACCEPTABLE_NETWORK_PARAMETER);
- add(SIGNAL_LOST);
- add(DUPLICATE_CID);
- add(MATCH_ALL_RULE_NOT_ALLOWED);
- add(ALL_MATCHING_RULES_FAILED);
- }
- };
+ permanentFailureSet = new HashSet<Integer>();
+ permanentFailureSet.add(OPERATOR_BARRED);
+ permanentFailureSet.add(MISSING_UNKNOWN_APN);
+ permanentFailureSet.add(UNKNOWN_PDP_ADDRESS_TYPE);
+ permanentFailureSet.add(USER_AUTHENTICATION);
+ permanentFailureSet.add(ACTIVATION_REJECT_GGSN);
+ permanentFailureSet.add(SERVICE_OPTION_NOT_SUPPORTED);
+ permanentFailureSet.add(SERVICE_OPTION_NOT_SUBSCRIBED);
+ permanentFailureSet.add(NSAPI_IN_USE);
+ permanentFailureSet.add(ONLY_IPV4_ALLOWED);
+ permanentFailureSet.add(ONLY_IPV6_ALLOWED);
+ permanentFailureSet.add(PROTOCOL_ERRORS);
+ permanentFailureSet.add(RADIO_POWER_OFF);
+ permanentFailureSet.add(TETHERED_CALL_ACTIVE);
+ permanentFailureSet.add(RADIO_NOT_AVAILABLE);
+ permanentFailureSet.add(UNACCEPTABLE_NETWORK_PARAMETER);
+ permanentFailureSet.add(SIGNAL_LOST);
+ permanentFailureSet.add(DUPLICATE_CID);
+ permanentFailureSet.add(MATCH_ALL_RULE_NOT_ALLOWED);
+ permanentFailureSet.add(ALL_MATCHING_RULES_FAILED);
}
permanentFailureSet.add(NO_RETRY_FAILURE);
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index 383561a..c352f2b 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -28,7 +28,6 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.ims.ImsReasonInfo;
@@ -703,9 +702,9 @@
}
@Override
- public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+ public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
try {
- mCallback.onDomainSelected(accessNetworkType);
+ mCallback.onDomainSelected(domain);
} catch (Exception e) {
Rlog.e(TAG, "onDomainSelected e=" + e);
}
@@ -835,7 +834,14 @@
return Runnable::run;
}
- private @NonNull Executor getCachedExecutor() {
+ /**
+ * Gets the {@link Executor} which executes methods of this service.
+ * This method should be private when this service is implemented in a separated process
+ * other than telephony framework.
+ * @return {@link Executor} instance.
+ * @hide
+ */
+ public @NonNull Executor getCachedExecutor() {
synchronized (mExecutorLock) {
if (mExecutor == null) {
Executor e = getExecutor();
diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index d4b912e..f433609 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -67,8 +67,8 @@
/** Call state: Disconnecting. */
public static final int PRECISE_CALL_STATE_DISCONNECTING = 8;
/**
- * Call state: Incoming in pre-alerting state.
- * A call will be in this state prior to entering {@link #PRECISE_CALL_STATE_ALERTING}.
+ * Call state: Incoming in pre-alerting state i.e. prior to entering
+ * {@link #PRECISE_CALL_STATE_INCOMING}.
*/
public static final int PRECISE_CALL_STATE_INCOMING_SETUP = 9;
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index a403095..9511db6 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -161,9 +161,17 @@
}
@Override
- public int hashCode () {
+ public int hashCode() {
return ((mRadioAccessNetwork * 31)
+ (Arrays.hashCode(mBands) * 37)
+ (Arrays.hashCode(mChannels)) * 39);
}
+
+ @Override
+ public String toString() {
+ return "RadioAccessSpecifier[mRadioAccessNetwork="
+ + AccessNetworkConstants.AccessNetworkType.toString(mRadioAccessNetwork)
+ + ", mBands=" + Arrays.toString(mBands)
+ + ", mChannels=" + Arrays.toString(mChannels) + "]";
+ }
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index bfa60ba..6be2f77 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,13 +138,6 @@
*/
public static final int FREQUENCY_RANGE_MMWAVE = 4;
- private static final List<Integer> FREQUENCY_RANGE_ORDER = Arrays.asList(
- FREQUENCY_RANGE_UNKNOWN,
- FREQUENCY_RANGE_LOW,
- FREQUENCY_RANGE_MID,
- FREQUENCY_RANGE_HIGH,
- FREQUENCY_RANGE_MMWAVE);
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "DUPLEX_MODE_",
@@ -2108,15 +2101,6 @@
}
/**
- * @hide
- */
- public static final int getBetterNRFrequencyRange(int range1, int range2) {
- return FREQUENCY_RANGE_ORDER.indexOf(range1) > FREQUENCY_RANGE_ORDER.indexOf(range2)
- ? range1
- : range2;
- }
-
- /**
* Returns a copy of self with location-identifying information removed.
* Always clears the NetworkRegistrationInfo's CellIdentity fields, but if removeCoarseLocation
* is true, clears other info as well.
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index c2b65f8..95d5136 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -1020,6 +1020,17 @@
}
/**
+ * Return the encoding type of a received SMS message, which is specified using ENCODING_*
+ * GSM: defined in android.telephony.SmsConstants
+ * CDMA: defined in android.telephony.cdma.UserData
+ *
+ * @hide
+ */
+ public int getReceivedEncodingType() {
+ return mWrappedSmsMessage.getReceivedEncodingType();
+ }
+
+ /**
* Determines whether or not to use CDMA format for MO SMS.
* If SMS over IMS is supported, then format is based on IMS SMS format,
* otherwise format is based on current phone type.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 76a145c..7f1a036 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2164,7 +2164,7 @@
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- subId = iSub.getSubId(slotIndex);
+ subId = iSub.getSubIds(slotIndex);
}
} catch (RemoteException ex) {
// ignore it
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 051fb74..8c3ef67 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2975,7 +2975,11 @@
public static final int NETWORK_TYPE_HSUPA = TelephonyProtoEnums.NETWORK_TYPE_HSUPA; // = 9.
/** Current network is HSPA */
public static final int NETWORK_TYPE_HSPA = TelephonyProtoEnums.NETWORK_TYPE_HSPA; // = 10.
- /** Current network is iDen */
+ /**
+ * Current network is iDen
+ * @deprecated Legacy network type no longer being used.
+ */
+ @Deprecated
public static final int NETWORK_TYPE_IDEN = TelephonyProtoEnums.NETWORK_TYPE_IDEN; // = 11.
/** Current network is EVDO revision B*/
public static final int NETWORK_TYPE_EVDO_B = TelephonyProtoEnums.NETWORK_TYPE_EVDO_B; // = 12.
@@ -8294,16 +8298,23 @@
/** Authentication type for UICC challenge is EAP AKA. See RFC 4187 for details. */
public static final int AUTHTYPE_EAP_AKA = PhoneConstants.AUTH_CONTEXT_EAP_AKA;
/**
- * Authentication type for GBA Bootstrap Challenge is GBA_BOOTSTRAP.
- * See 3GPP 33.220 Section 5.3.2.
- * @hide
+ * Authentication type for GBA Bootstrap Challenge.
+ * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+ * Bootstrap challenge (BSF), with {@code data} (generated according to the procedure defined in
+ * 3GPP 33.220 Section 5.3.2 step.4) in base64 encoding.
+ * This method will return the Bootstrapping response in base64 encoding when ICC authentication
+ * is completed.
+ * Ref 3GPP 33.220 Section 5.3.2.
*/
public static final int AUTHTYPE_GBA_BOOTSTRAP = PhoneConstants.AUTH_CONTEXT_GBA_BOOTSTRAP;
/**
- * Authentication type for GBA Network Application Functions (NAF) key
- * External Challenge is AUTHTYPE_GBA_NAF_KEY_EXTERNAL.
- * See 3GPP 33.220 Section 5.3.2.
- * @hide
+ * Authentication type for GBA Network Application Functions (NAF) key External Challenge.
+ * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+ * Network Applications Functions (NAF) key External challenge using the NAF_ID parameter
+ * as the {@code data} in base64 encoding.
+ * This method will return the Ks_Ext_Naf key in base64 encoding when ICC authentication
+ * is completed.
+ * Ref 3GPP 33.220 Section 5.3.2.
*/
public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL =
PhoneConstants.AUTHTYPE_GBA_NAF_KEY_EXTERNAL;
@@ -8332,7 +8343,8 @@
*
* @param appType the icc application type, like {@link #APPTYPE_USIM}
* @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
- * {@link #AUTHTYPE_EAP_SIM}
+ * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+ * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
* @return the response of authentication. This value will be null in the following cases:
@@ -8360,7 +8372,8 @@
* @param subId subscription ID used for authentication
* @param appType the icc application type, like {@link #APPTYPE_USIM}
* @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
- * {@link #AUTHTYPE_EAP_SIM}
+ * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+ * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
* @return the response of authentication. This value will be null in the following cases only
@@ -9527,12 +9540,13 @@
/**
* Set the allowed network types of the device and provide the reason triggering the allowed
* network change.
- * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE or
+ * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
* that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
- * This can be called for following reasons
+ * This can be called for following reasons:
* <ol>
- * <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER}
+ * <li>Allowed network types control by USER
+ * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER}
* <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}
* </ol>
* This API will result in allowing an intersection of allowed network types for all reasons,
@@ -9542,7 +9556,13 @@
* @param allowedNetworkTypes The bitmask of allowed network type
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
- * @throws SecurityException if the caller does not have the required privileges
+ * @throws SecurityException if the caller does not have the required privileges or if the
+ * caller tries to use one of the following security-based reasons without
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
+ * <ol>
+ * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
+ * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li>
+ * </ol>
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(
@@ -12526,7 +12546,7 @@
Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
} catch (NullPointerException e) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
"getServiceStateForSubscriber " + subId + " NPE");
}
return null;
@@ -13983,6 +14003,7 @@
public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1));
/**
* network type bitmask indicating the support of radio tech iDen.
+ * @hide
*/
public static final long NETWORK_TYPE_BITMASK_IDEN = (1 << (NETWORK_TYPE_IDEN - 1));
/**
@@ -14952,21 +14973,132 @@
* @return a Pair of (major version, minor version) or (-1,-1) if unknown.
*
* @hide
+ *
+ * @deprecated Use {@link #getHalVersion} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
@TestApi
public Pair<Integer, Integer> getRadioHalVersion() {
+ return getHalVersion(HAL_SERVICE_RADIO);
+ }
+
+ /** @hide */
+ public static final int HAL_SERVICE_RADIO = 0;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioData
+ * {@link RadioDataProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_DATA = 1;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioMessaging
+ * {@link RadioMessagingProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_MESSAGING = 2;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioModem
+ * {@link RadioModemProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_MODEM = 3;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioNetwork
+ * {@link RadioNetworkProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_NETWORK = 4;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioSim
+ * {@link RadioSimProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_SIM = 5;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioVoice
+ * {@link RadioVoiceProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_VOICE = 6;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioIms
+ * {@link RadioImsProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_IMS = 7;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"HAL_SERVICE_"},
+ value = {
+ HAL_SERVICE_RADIO,
+ HAL_SERVICE_DATA,
+ HAL_SERVICE_MESSAGING,
+ HAL_SERVICE_MODEM,
+ HAL_SERVICE_NETWORK,
+ HAL_SERVICE_SIM,
+ HAL_SERVICE_VOICE,
+ HAL_SERVICE_IMS,
+ })
+ public @interface HalService {}
+
+ /**
+ * The HAL Version indicating that the version is unknown or invalid.
+ * @hide
+ */
+ @TestApi
+ public static final Pair HAL_VERSION_UNKNOWN = new Pair(-1, -1);
+
+ /**
+ * The HAL Version indicating that the version is unsupported.
+ * @hide
+ */
+ @TestApi
+ public static final Pair HAL_VERSION_UNSUPPORTED = new Pair(-2, -2);
+
+ /**
+ * Retrieve the HAL Version of a specific service for this device.
+ *
+ * Get the HAL version for a specific HAL interface for test purposes.
+ *
+ * @param halService the service id to query.
+ * @return a Pair of (major version, minor version), HAL_VERSION_UNKNOWN if unknown
+ * or HAL_VERSION_UNSUPPORTED if unsupported.
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull Pair<Integer, Integer> getHalVersion(@HalService int halService) {
try {
ITelephony service = getITelephony();
if (service != null) {
- int version = service.getRadioHalVersion();
- if (version == -1) return new Pair<Integer, Integer>(-1, -1);
- return new Pair<Integer, Integer>(version / 100, version % 100);
+ int version = service.getHalVersion(halService);
+ if (version != -1) {
+ return new Pair<Integer, Integer>(version / 100, version % 100);
+ }
+ } else {
+ throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException e) {
- Log.e(TAG, "getRadioHalVersion() RemoteException", e);
+ Log.e(TAG, "getHalVersion() RemoteException", e);
+ e.rethrowAsRuntimeException();
}
- return new Pair<Integer, Integer>(-1, -1);
+ return HAL_VERSION_UNKNOWN;
}
/**
@@ -17319,14 +17451,14 @@
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12;
/**
- * Purchase premium capability failed because the network is congested.
+ * Purchase premium capability failed because the entitlement check failed.
* Subsequent attempts will be throttled for the amount of time specified by
* {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
* and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
* Throttling will be reevaluated when the network is no longer congested.
*/
- public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13;
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13;
/**
* Purchase premium capability failed because the request was not made on the default data
@@ -17334,7 +17466,7 @@
* Subsequent attempts will return the same error until the request is made on the default
* data subscription.
*/
- public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14;
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 14;
/**
* Purchase premium capability was successful and is waiting for the network to setup the
@@ -17363,8 +17495,8 @@
PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT,
PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
public @interface PurchasePremiumCapabilityResult {}
@@ -17402,10 +17534,10 @@
return "REQUEST_FAILED";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE:
return "NETWORK_NOT_AVAILABLE";
- case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED:
- return "NETWORK_CONGESTED";
- case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB:
- return "NOT_DEFAULT_DATA_SUB";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED:
+ return "ENTITLEMENT_CHECK_FAILED";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+ return "NOT_DEFAULT_DATA_SUBSCRIPTION";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
return "PENDING_NETWORK_SETUP";
default:
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index 489a589..b3682ca 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.os.CancellationSignal;
-import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
import android.telephony.DomainSelectionService.EmergencyScanType;
import java.util.List;
@@ -46,7 +45,7 @@
* Notifies the FW that the domain has been selected. After this method is called,
* this interface can be discarded.
*
- * @param accessNetworkType the selected network type.
+ * @param domain The selected domain.
*/
- void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+ void onDomainSelected(@NetworkRegistrationInfo.Domain int domain);
}
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 73aff43..a834e2bb 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -468,14 +468,14 @@
final boolean isQosBearerSessionsSame =
(mQosBearerSessions == null || other.mQosBearerSessions == null)
? mQosBearerSessions == other.mQosBearerSessions
- : mQosBearerSessions.size() == other.mQosBearerSessions.size()
- && mQosBearerSessions.containsAll(other.mQosBearerSessions);
+ : (mQosBearerSessions.size() == other.mQosBearerSessions.size()
+ && mQosBearerSessions.containsAll(other.mQosBearerSessions));
final boolean isTrafficDescriptorsSame =
(mTrafficDescriptors == null || other.mTrafficDescriptors == null)
? mTrafficDescriptors == other.mTrafficDescriptors
- : mTrafficDescriptors.size() == other.mTrafficDescriptors.size()
- && mTrafficDescriptors.containsAll(other.mTrafficDescriptors);
+ : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size()
+ && mTrafficDescriptors.containsAll(other.mTrafficDescriptors));
return mCause == other.mCause
&& mSuggestedRetryTime == other.mSuggestedRetryTime
@@ -504,10 +504,35 @@
@Override
public int hashCode() {
+ // Generate order-independent hashes for lists
+ int addressesHash = mAddresses.stream()
+ .map(LinkAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int dnsAddressesHash = mDnsAddresses.stream()
+ .map(InetAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int gatewayAddressesHash = mGatewayAddresses.stream()
+ .map(InetAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int pcscfAddressesHash = mPcscfAddresses.stream()
+ .map(InetAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int qosBearerSessionsHash = mQosBearerSessions.stream()
+ .map(QosBearerSession::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int trafficDescriptorsHash = mTrafficDescriptors.stream()
+ .map(TrafficDescriptor::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
- mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
- mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos,
- mQosBearerSessions, mSliceInfo, mTrafficDescriptors);
+ mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash,
+ pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
+ mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash);
}
@Override
@@ -816,8 +841,8 @@
/**
* Set pdu session id.
* <p/>
- * The id must be between 1 and 15 when linked to a pdu session. If no pdu session
- * exists for the current data call, the id must be set to {@link PDU_SESSION_ID_NOT_SET}.
+ * The id must be between 1 and 15 when linked to a pdu session. If no pdu session
+ * exists for the current data call, the id must be set to {@link #PDU_SESSION_ID_NOT_SET}.
*
* @param pduSessionId Pdu Session Id of the data call.
* @return The same instance of the builder.
@@ -858,6 +883,7 @@
*/
public @NonNull Builder setQosBearerSessions(
@NonNull List<QosBearerSession> qosBearerSessions) {
+ Objects.requireNonNull(qosBearerSessions);
mQosBearerSessions = qosBearerSessions;
return this;
}
@@ -891,6 +917,7 @@
*/
public @NonNull Builder setTrafficDescriptors(
@NonNull List<TrafficDescriptor> trafficDescriptors) {
+ Objects.requireNonNull(trafficDescriptors);
mTrafficDescriptors = trafficDescriptors;
return this;
}
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
index ba2b62d..8e27077 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
@@ -27,4 +27,5 @@
oneway void createNetworkAvailabilityProvider(int slotId, IQualifiedNetworksServiceCallback callback);
oneway void removeNetworkAvailabilityProvider(int slotId);
oneway void reportThrottleStatusChanged(int slotId, in List<ThrottleStatus> statuses);
+ oneway void reportEmergencyDataNetworkPreferredTransportChanged (int slotId, int transportType);
}
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 0ab7b61..a0d9c1bd 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -130,6 +130,10 @@
return precedence;
}
+ public int getProtocol() {
+ return protocol;
+ }
+
public static class PortRange implements Parcelable {
int start;
int end;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index fb97336..56f0f9f 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -68,6 +68,7 @@
private static final int QNS_REMOVE_ALL_NETWORK_AVAILABILITY_PROVIDERS = 3;
private static final int QNS_UPDATE_QUALIFIED_NETWORKS = 4;
private static final int QNS_APN_THROTTLE_STATUS_CHANGED = 5;
+ private static final int QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED = 6;
private final HandlerThread mHandlerThread;
@@ -193,6 +194,20 @@
}
/**
+ * The framework calls this method when the preferred transport type used to set up
+ * emergency data network is changed.
+ *
+ * This method is meant to be overridden.
+ *
+ * @param transportType transport type changed to be preferred
+ */
+ public void reportEmergencyDataNetworkPreferredTransportChanged(
+ @AccessNetworkConstants.TransportType int transportType) {
+ Log.d(TAG, "reportEmergencyDataNetworkPreferredTransportChanged: "
+ + AccessNetworkConstants.transportTypeToString(transportType));
+ }
+
+ /**
* Called when the qualified networks provider is removed. The extended class should
* implement this method to perform cleanup works.
*/
@@ -237,6 +252,13 @@
}
break;
+ case QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED:
+ if (provider != null) {
+ int transportType = (int) message.arg2;
+ provider.reportEmergencyDataNetworkPreferredTransportChanged(transportType);
+ }
+ break;
+
case QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER:
if (provider != null) {
provider.close();
@@ -332,6 +354,14 @@
mHandler.obtainMessage(QNS_APN_THROTTLE_STATUS_CHANGED, slotIndex, 0, statuses)
.sendToTarget();
}
+
+ @Override
+ public void reportEmergencyDataNetworkPreferredTransportChanged(int slotIndex,
+ @AccessNetworkConstants.TransportType int transportType) {
+ mHandler.obtainMessage(
+ QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED,
+ slotIndex, transportType).sendToTarget();
+ }
}
private void log(String s) {
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index d9d5c14..e78a1e1 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -660,9 +660,6 @@
if (!first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
return false;
}
- if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) {
- return false;
- }
// Never merge two numbers if one of them is from test mode but the other one is not;
// This supports to remove a number from the test mode.
if (first.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST)
@@ -685,12 +682,18 @@
public static EmergencyNumber mergeSameEmergencyNumbers(@NonNull EmergencyNumber first,
@NonNull EmergencyNumber second) {
if (areSameEmergencyNumbers(first, second)) {
+ int routing = first.getEmergencyCallRouting();
+
+ if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
+ routing = second.getEmergencyCallRouting();
+ }
+
return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
first.getEmergencyServiceCategoryBitmask(),
first.getEmergencyUrns(),
first.getEmergencyNumberSourceBitmask()
| second.getEmergencyNumberSourceBitmask(),
- first.getEmergencyCallRouting());
+ routing);
}
return null;
}
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 0a2bb3d..e61d1e6 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -118,7 +118,10 @@
/** Resets the default SM-DP+ address. */
public static final int RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS = 1 << 2;
- /** Result code when the requested profile is not found */
+ /** Result code when the requested profile is not found
+ * @deprecated use {@link #RESULT_PROFILE_DOES_NOT_EXIST}
+ **/
+ @Deprecated
public static final int RESULT_PROFILE_NOT_FOUND = 1;
/** Result code of execution with no error. */
@@ -133,6 +136,9 @@
/** Result code indicating the caller is not the active LPA. */
public static final int RESULT_CALLER_NOT_ALLOWED = -3;
+ /** Result code when the requested profile does not exist */
+ public static final int RESULT_PROFILE_DOES_NOT_EXIST = -4;
+
/**
* Callback to receive the result of an eUICC card API.
*
@@ -224,7 +230,7 @@
/**
* Requests the enabled profile for a given port on an eUicc. Callback with result code
- * {@link RESULT_PROFILE_NOT_FOUND} and {@code NULL} EuiccProfile if there is no enabled
+ * {@link RESULT_PROFILE_DOES_NOT_EXIST} and {@code NULL} EuiccProfile if there is no enabled
* profile on the target port.
*
* @param cardId The Id of the eUICC.
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index a3cbb4a..33c86d8 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -50,7 +50,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CancellationException;
@@ -179,10 +178,9 @@
* Used for logging purposes, see {@link #getCapabilitiesString(long)}
* @hide
*/
- private static final Map<Long, String> CAPABILITIES_LOG_MAP = new HashMap<Long, String>() {{
- put(CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL");
- put(CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
- }};
+ private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
+ CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
+ CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
/**
* The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 090d413..9996b86 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -78,24 +78,22 @@
/**@hide*/
// Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
// and WWAN are more accurate constants.
- Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP =
- new HashMap<Integer, Integer>() {{
- // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
- // case, since it is defined.
- put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
- AccessNetworkConstants.TRANSPORT_TYPE_INVALID);
- put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- put(ImsRegistrationImplBase.REGISTRATION_TECH_NR,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
- /* As the cross sim will be using ePDG tunnel over internet, it behaves
- like IWLAN in most cases. Hence setting the access type as IWLAN
- */
- put(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
- }};
+ Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP = Map.of(
+ // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
+ // case, since it is defined.
+ ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+ AccessNetworkConstants.TRANSPORT_TYPE_INVALID,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ ImsRegistrationImplBase.REGISTRATION_TECH_NR,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+ /* As the cross sim will be using ePDG tunnel over internet, it behaves
+ like IWLAN in most cases. Hence setting the access type as IWLAN
+ */
+ ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
/** @hide */
@NonNull
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index a42327b..174675f 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -34,7 +34,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -85,11 +84,10 @@
* Used for logging purposes.
* @hide
*/
- public static final Map<Integer, String> FEATURE_LOG_MAP = new HashMap<Integer, String>() {{
- put(FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL");
- put(FEATURE_MMTEL, "MMTEL");
- put(FEATURE_RCS, "RCS");
- }};
+ public static final Map<Integer, String> FEATURE_LOG_MAP = Map.of(
+ FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL",
+ FEATURE_MMTEL, "MMTEL",
+ FEATURE_RCS, "RCS");
/**
* Integer values defining IMS features that are supported in ImsFeature.
@@ -145,11 +143,10 @@
* Used for logging purposes.
* @hide
*/
- public static final Map<Integer, String> STATE_LOG_MAP = new HashMap<Integer, String>() {{
- put(STATE_UNAVAILABLE, "UNAVAILABLE");
- put(STATE_INITIALIZING, "INITIALIZING");
- put(STATE_READY, "READY");
- }};
+ public static final Map<Integer, String> STATE_LOG_MAP = Map.of(
+ STATE_UNAVAILABLE, "UNAVAILABLE",
+ STATE_INITIALIZING, "INITIALIZING",
+ STATE_READY, "READY");
/**
* Integer values defining the result codes that should be returned from
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
old mode 100755
new mode 100644
index 4752cca..4fef4d7
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -244,7 +244,7 @@
int getSlotIndex(int subId);
- int[] getSubId(int slotIndex);
+ int[] getSubIds(int slotIndex);
int getDefaultSubId();
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 648866b..abf4cde 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2159,6 +2159,12 @@
int getRadioHalVersion();
/**
+ * Get the HAL Version of a specific service
+ * encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown
+ */
+ int getHalVersion(int service);
+
+ /**
* Get the current calling package name.
*/
String getCurrentPackageName();
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index 65d994b..339fbee 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -21,6 +21,6 @@
oneway interface IWwanSelectorCallback {
void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
int scanType, in IWwanSelectorResultCallback cb);
- void onDomainSelected(int accessNetworkType);
+ void onDomainSelected(int domain);
void onCancel();
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9f612e6..0c14dba 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -542,6 +542,10 @@
int RIL_REQUEST_STOP_IMS_TRAFFIC = 236;
int RIL_REQUEST_SEND_ANBR_QUERY = 237;
int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238;
+ int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239;
+ int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
+ int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
+ int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
index 6d46ed3..0cc1e98 100644
--- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -16,6 +16,8 @@
package com.android.internal.telephony;
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.telephony.SmsMessage;
@@ -94,6 +96,15 @@
protected boolean mMwiDontStore;
/**
+ * The encoding type of a received SMS message, which is specified using ENCODING_*
+ * GSM: defined in android.telephony.SmsConstants
+ * CDMA: defined in android.telephony.cdma.UserData
+ *
+ * @hide
+ */
+ protected int mReceivedEncodingType = ENCODING_UNKNOWN;
+
+ /**
* Indicates status for messages stored on the ICC.
*/
protected int mStatusOnIcc = -1;
@@ -512,4 +523,8 @@
return mRecipientAddress.getAddressString();
}
+
+ public int getReceivedEncodingType() {
+ return mReceivedEncodingType;
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index f636276..b51ba31 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -780,6 +780,7 @@
mUserData = mBearerData.userData.payload;
mUserDataHeader = mBearerData.userData.userDataHeader;
mMessageBody = mBearerData.userData.payloadStr;
+ mReceivedEncodingType = mBearerData.userData.msgEncoding;
}
if (mOriginatingAddress != null) {
@@ -860,6 +861,9 @@
Rlog.w(LOG_TAG, "BearerData.decode() returned null");
return null;
}
+ if (bData.userData != null) {
+ mReceivedEncodingType = bData.userData.msgEncoding;
+ }
if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 7a5bf06..09ead31 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -1462,28 +1462,28 @@
} else {
switch ((mDataCodingScheme >> 2) & 0x3) {
case 0: // GSM 7 bit default alphabet
- encodingType = ENCODING_7BIT;
- break;
+ encodingType = ENCODING_7BIT;
+ break;
case 2: // UCS 2 (16bit)
- encodingType = ENCODING_16BIT;
- break;
+ encodingType = ENCODING_16BIT;
+ break;
case 1: // 8 bit data
- //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
- //that's stored in 8-bit unpacked format) characters.
- if (r.getBoolean(com.android.internal.
- R.bool.config_sms_decode_gsm_8bit_data)) {
- encodingType = ENCODING_8BIT;
- break;
- }
+ // Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet
+ // string that's stored in 8-bit unpacked format) characters.
+ if (r.getBoolean(com.android.internal
+ .R.bool.config_sms_decode_gsm_8bit_data)) {
+ encodingType = ENCODING_8BIT;
+ break;
+ }
case 3: // reserved
- Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
- + (mDataCodingScheme & 0xff));
- encodingType = r.getInteger(
- com.android.internal.R.integer.default_reserved_data_coding_scheme);
- break;
+ Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ encodingType = r.getInteger(
+ com.android.internal.R.integer.default_reserved_data_coding_scheme);
+ break;
}
}
} else if ((mDataCodingScheme & 0xf0) == 0xf0) {
@@ -1558,6 +1558,7 @@
encodingType == ENCODING_7BIT);
this.mUserData = p.getUserData();
this.mUserDataHeader = p.getUserDataHeader();
+ this.mReceivedEncodingType = encodingType;
/*
* Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
index 0f4e122..4bcf5a4 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
@@ -16,14 +16,16 @@
package com.android.test.hwuicompare;
-import java.util.LinkedHashMap;
-import java.util.Map.Entry;
+import static java.util.Map.entry;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.Log;
+import java.util.Map;
+import java.util.Map.Entry;
+
public abstract class DisplayModifier {
// automated tests ignore any combination of operations that don't together return TOTAL_MASK
@@ -76,41 +78,36 @@
};
@SuppressWarnings("serial")
- private static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> gMaps = new LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>>() {
- {
- put("aa", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("true", new DisplayModifier() {
+ private static final Map<String, Map<String, DisplayModifier>> gMaps = Map.of(
+ "aa", Map.of(
+ "true", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setAntiAlias(true);
}
- });
- put("false", new DisplayModifier() {
+ },
+ "false", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setAntiAlias(false);
}
- });
- }
- });
- put("style", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("fill", new DisplayModifier() {
+ }),
+ "style", Map.of(
+ "fill", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
}
- });
- put("stroke", new DisplayModifier() {
+ },
+ "stroke", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStyle(Paint.Style.STROKE);
}
@Override
protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
- });
- put("fillAndStroke", new DisplayModifier() {
+ },
+ "fillAndStroke", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStyle(Paint.Style.FILL_AND_STROKE);
@@ -118,131 +115,118 @@
@Override
protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
- });
- }
- });
- put("strokeWidth", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("hair", new DisplayModifier() {
+ }),
+ "strokeWidth", Map.of(
+ "hair", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(0);
}
@Override
protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
- });
- put("0.3", new DisplayModifier() {
+ },
+ "0.3", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(0.3f);
}
- });
- put("1", new DisplayModifier() {
+ },
+ "1", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(1);
}
- });
- put("5", new DisplayModifier() {
+ },
+ "5", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(5);
}
- });
- put("30", new DisplayModifier() {
+ },
+ "30", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(30);
}
- });
- }
- });
- put("strokeCap", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("butt", new DisplayModifier() {
+ }),
+ "strokeCap", Map.of(
+ "butt", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeCap(Paint.Cap.BUTT);
}
@Override
protected int mask() { return SWEEP_STROKE_CAP_BIT; }
- });
- put("round", new DisplayModifier() {
+ },
+ "round", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeCap(Paint.Cap.ROUND);
}
- });
- put("square", new DisplayModifier() {
+ },
+ "square", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeCap(Paint.Cap.SQUARE);
}
- });
- }
- });
- put("strokeJoin", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("bevel", new DisplayModifier() {
+ }),
+ "strokeJoin", Map.of(
+ "bevel", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeJoin(Paint.Join.BEVEL);
}
@Override
protected int mask() { return SWEEP_STROKE_JOIN_BIT; }
- });
- put("round", new DisplayModifier() {
+ },
+ "round", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeJoin(Paint.Join.ROUND);
}
- });
- put("miter", new DisplayModifier() {
+ },
+ "miter", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeJoin(Paint.Join.MITER);
}
- });
+ }),
// TODO: add miter0, miter1 etc to test miter distances
- }
- });
-
- put("transform", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("noTransform", new DisplayModifier() {
+ "transform", Map.of(
+ "noTransform", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {}
@Override
protected int mask() { return SWEEP_TRANSFORM_BIT; };
- });
- put("rotate5", new DisplayModifier() {
+ },
+ "rotate5", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(5);
}
- });
- put("rotate45", new DisplayModifier() {
+ },
+ "rotate45", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(45);
}
- });
- put("rotate90", new DisplayModifier() {
+ },
+ "rotate90", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(90);
canvas.translate(0, -200);
}
- });
- put("scale2x2", new DisplayModifier() {
+ },
+ "scale2x2", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.scale(2, 2);
}
@Override
protected int mask() { return SWEEP_TRANSFORM_BIT; };
- });
- put("rot20scl1x4", new DisplayModifier() {
+ },
+ "rot20scl1x4", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(20);
@@ -250,180 +234,167 @@
}
@Override
protected int mask() { return SWEEP_TRANSFORM_BIT; };
- });
- }
- });
-
- put("shader", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("noShader", new DisplayModifier() {
+ }),
+ "shader", Map.ofEntries(
+ entry("noShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {}
@Override
protected int mask() { return SWEEP_SHADER_BIT; };
- });
- put("repeatShader", new DisplayModifier() {
+ }),
+ entry("repeatShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mRepeatShader);
}
@Override
protected int mask() { return SWEEP_SHADER_BIT; };
- });
- put("translatedShader", new DisplayModifier() {
+ }),
+ entry("translatedShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mTranslatedShader);
}
- });
- put("scaledShader", new DisplayModifier() {
+ }),
+ entry("scaledShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mScaledShader);
}
- });
- put("horGradient", new DisplayModifier() {
+ }),
+ entry("horGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mHorGradient);
}
- });
- put("diagGradient", new DisplayModifier() {
+ }),
+ entry("diagGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mDiagGradient);
}
@Override
protected int mask() { return SWEEP_SHADER_BIT; };
- });
- put("vertGradient", new DisplayModifier() {
+ }),
+ entry("vertGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mVertGradient);
}
- });
- put("radGradient", new DisplayModifier() {
+ }),
+ entry("radGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mRadGradient);
}
- });
- put("sweepGradient", new DisplayModifier() {
+ }),
+ entry("sweepGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mSweepGradient);
}
- });
- put("composeShader", new DisplayModifier() {
+ }),
+ entry("composeShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mComposeShader);
}
- });
- put("bad composeShader", new DisplayModifier() {
+ }),
+ entry("bad composeShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mBadComposeShader);
}
- });
- put("bad composeShader 2", new DisplayModifier() {
+ }),
+ entry("bad composeShader 2", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mAnotherBadComposeShader);
}
- });
- }
- });
-
- // FINAL MAP: DOES ACTUAL DRAWING
- put("drawing", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("roundRect", new DisplayModifier() {
+ })),
+ "drawing", Map.ofEntries(
+ entry("roundRect", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawRoundRect(gRect, 20, 20, paint);
}
- });
- put("rect", new DisplayModifier() {
+ }),
+ entry("rect", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawRect(gRect, paint);
}
@Override
protected int mask() { return SWEEP_SHADER_BIT | SWEEP_STROKE_CAP_BIT; };
- });
- put("circle", new DisplayModifier() {
+ }),
+ entry("circle", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawCircle(100, 100, 75, paint);
}
- });
- put("oval", new DisplayModifier() {
+ }),
+ entry("oval", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawOval(gRect, paint);
}
- });
- put("lines", new DisplayModifier() {
+ }),
+ entry("lines", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawLines(gLinePts, paint);
}
@Override
protected int mask() { return SWEEP_STROKE_CAP_BIT; };
- });
- put("plusPoints", new DisplayModifier() {
+ }),
+ entry("plusPoints", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawPoints(gPts, paint);
}
- });
- put("text", new DisplayModifier() {
+ }),
+ entry("text", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setTextSize(36);
canvas.drawText("TEXTTEST", 0, 50, paint);
}
- });
- put("shadowtext", new DisplayModifier() {
+ }),
+ entry("shadowtext", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setTextSize(36);
paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff);
canvas.drawText("TEXTTEST", 0, 50, paint);
}
- });
- put("bitmapMesh", new DisplayModifier() {
+ }),
+ entry("bitmapMesh", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawBitmapMesh(ResourceModifiers.instance().mBitmap, 3, 3,
ResourceModifiers.instance().mBitmapVertices, 0, null, 0, null);
}
- });
- put("arc", new DisplayModifier() {
+ }),
+ entry("arc", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawArc(gRect, 260, 285, false, paint);
}
@Override
protected int mask() { return SWEEP_STROKE_CAP_BIT; };
- });
- put("arcFromCenter", new DisplayModifier() {
+ }),
+ entry("arcFromCenter", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawArc(gRect, 260, 285, true, paint);
}
@Override
protected int mask() { return SWEEP_STROKE_JOIN_BIT; };
- });
- }
- });
+ })));
// WARNING: DON'T PUT MORE MAPS BELOW THIS
- }
- };
- private static LinkedHashMap<String, DisplayModifier> getMapAtIndex(int index) {
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ private static Map<String, DisplayModifier> getMapAtIndex(int index) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
if (index == 0) {
return map;
}
@@ -439,7 +410,7 @@
private static boolean stepInternal(boolean forward) {
int modifierMapIndex = gMaps.size() - 1;
while (modifierMapIndex >= 0) {
- LinkedHashMap<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
+ Map<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
mIndices[modifierMapIndex] += (forward ? 1 : -1);
if (mIndices[modifierMapIndex] >= 0 && mIndices[modifierMapIndex] < map.size()) {
@@ -471,7 +442,7 @@
private static boolean checkModificationStateMask() {
int operatorMask = 0x0;
int mapIndex = 0;
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
int displayModifierIndex = mIndices[mapIndex];
for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
if (displayModifierIndex == 0) {
@@ -488,7 +459,7 @@
public static void apply(Paint paint, Canvas canvas) {
int mapIndex = 0;
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
int displayModifierIndex = mIndices[mapIndex];
for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
if (displayModifierIndex == 0) {
@@ -510,7 +481,7 @@
String[][] keys = new String[gMaps.size()][];
int i = 0;
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
keys[i] = new String[map.size()];
int j = 0;
for (String key : map.keySet()) {
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index d91aa1e..a7d6a01 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -15,6 +15,8 @@
<option name="run-command" value="cmd window tracing frame" />
<!-- ensure lock screen mode is swipe -->
<option name="run-command" value="locksettings set-disabled false" />
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
<!-- restart launcher to activate TAPL -->
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
</target_preparer>
diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
index 2ad0da9..8b9c020 100644
--- a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
+++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
@@ -58,7 +58,7 @@
Object hash = getProperty(props, "__hash__");
if (name instanceof String && hash instanceof Integer) {
- return String.format(Locale.US, "%s@%x", name, hash);
+ return String.format(Locale.US, "%s@%x", name, (Integer) hash);
} else {
return null;
}
diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
new file mode 100644
index 0000000..0f96634
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.internal.os;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+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 androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TimeoutRecord}. */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TimeoutRecordTest {
+
+ @Test
+ public void forBroadcastReceiver_returnsCorrectTimeoutRecord() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+ TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+ assertEquals(record.mReason,
+ "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+ + ".app/ExampleClass }");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forBroadcastReceiver_withTimeoutDurationMs_returnsCorrectTimeoutRecord() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+ TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, 1000L);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+ assertEquals(record.mReason,
+ "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+ + ".app/ExampleClass }, waited 1000ms");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forInputDispatchNoFocusedWindow_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forInputDispatchNoFocusedWindow("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW);
+ assertEquals(record.mReason,
+ "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forInputDispatchWindowUnresponsive_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forInputDispatchWindowUnresponsive("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forServiceExec_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forServiceExec("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_EXEC);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forServiceStartWithEndTime_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forServiceStartWithEndTime("Test ANR reason", 1000L);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_START);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertEquals(record.mEndUptimeMillis, 1000L);
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forContentProvider_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forContentProvider("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.CONTENT_PROVIDER);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertFalse(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forApp_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forApp("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.APP_REGISTERED);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertFalse(record.mEndTakenBeforeLocks);
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
index 4de51fb..43dc9de 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
@@ -140,9 +140,9 @@
handleNextBenchmark();
}
+ @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-
}
private void handleNextBenchmark() {
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
index c16efbd..d015a56 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -367,6 +367,7 @@
}
}
+ @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
index dd9b294..afaeca1 100644
--- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
+++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
@@ -15,7 +15,6 @@
*/
package com.android.frameworks.perftests.job;
-
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -46,7 +45,8 @@
public class JobStorePerfTests {
private static final String SOURCE_PACKAGE = "com.android.frameworks.perftests.job";
private static final int SOURCE_USER_ID = 0;
- private static final int CALLING_UID = 10079;
+ private static final int BASE_CALLING_UID = 10079;
+ private static final int MAX_UID_COUNT = 10;
private static Context sContext;
private static File sTestDir;
@@ -65,10 +65,10 @@
sJobStore = JobStore.initAndGetForTesting(sContext, sTestDir);
for (int i = 0; i < 50; i++) {
- sFewJobs.add(createJobStatus("fewJobs", i));
+ sFewJobs.add(createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT)));
}
for (int i = 0; i < 500; i++) {
- sManyJobs.add(createJobStatus("manyJobs", i));
+ sManyJobs.add(createJobStatus("manyJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT)));
}
}
@@ -104,6 +104,64 @@
runPersistedJobWriting(sManyJobs);
}
+ private void runPersistedJobWriting_delta(List<JobStatus> jobList,
+ List<JobStatus> jobAdditions, List<JobStatus> jobRemovals) {
+ final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+
+ long elapsedTimeNs = 0;
+ while (benchmarkState.keepRunning(elapsedTimeNs)) {
+ sJobStore.clearForTesting();
+ for (JobStatus job : jobList) {
+ sJobStore.addForTesting(job);
+ }
+ sJobStore.writeStatusToDiskForTesting();
+
+ for (JobStatus job : jobAdditions) {
+ sJobStore.addForTesting(job);
+ }
+ for (JobStatus job : jobRemovals) {
+ sJobStore.removeForTesting(job);
+ }
+
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ sJobStore.writeStatusToDiskForTesting();
+ final long endTime = SystemClock.elapsedRealtimeNanos();
+ elapsedTimeNs = endTime - startTime;
+ }
+ }
+
+ @Test
+ public void testPersistedJobWriting_delta_fewJobs() {
+ List<JobStatus> additions = new ArrayList<>();
+ List<JobStatus> removals = new ArrayList<>();
+ final int numModifiedUids = MAX_UID_COUNT / 2;
+ for (int i = 0; i < sFewJobs.size() / 3; ++i) {
+ JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids));
+ if (i % 2 == 0) {
+ additions.add(job);
+ } else {
+ removals.add(job);
+ }
+ }
+ runPersistedJobWriting_delta(sFewJobs, additions, removals);
+ }
+
+ @Test
+ public void testPersistedJobWriting_delta_manyJobs() {
+ List<JobStatus> additions = new ArrayList<>();
+ List<JobStatus> removals = new ArrayList<>();
+ final int numModifiedUids = MAX_UID_COUNT / 2;
+ for (int i = 0; i < sManyJobs.size() / 3; ++i) {
+ JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids));
+ if (i % 2 == 0) {
+ additions.add(job);
+ } else {
+ removals.add(job);
+ }
+ }
+ runPersistedJobWriting_delta(sManyJobs, additions, removals);
+ }
+
private void runPersistedJobReading(List<JobStatus> jobList, boolean rtcIsGood) {
final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
@@ -144,12 +202,12 @@
runPersistedJobReading(sManyJobs, false);
}
- private static JobStatus createJobStatus(String testTag, int jobId) {
+ private static JobStatus createJobStatus(String testTag, int jobId, int callingUid) {
JobInfo jobInfo = new JobInfo.Builder(jobId,
new ComponentName(sContext, "JobStorePerfTestJobService"))
.setPersisted(true)
.build();
return JobStatus.createFromJobInfo(
- jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
}
}
diff --git a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
index 8afe841..17fa210 100644
--- a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
+++ b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
@@ -295,8 +295,8 @@
private void updateMirror(Rect displayFrame, float scale) {
if (displayFrame.isEmpty()) {
Rect bounds = mWindowBounds;
- int defaultCropW = Math.round(bounds.width() / 2);
- int defaultCropH = Math.round(bounds.height() / 2);
+ int defaultCropW = bounds.width() / 2;
+ int defaultCropH = bounds.height() / 2;
displayFrame.set(0, 0, defaultCropW, defaultCropH);
}
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index 241206d..65b7549 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -24,18 +24,14 @@
static final String KEY_NAME = "name";
static final String KEY_CLASS = "clazz";
- static Map<String,?> make(String name) {
- Map<String,Object> ret = new HashMap<String,Object>();
- ret.put(KEY_NAME, name);
- return ret;
- }
-
- @SuppressWarnings("serial")
- static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+ static final ArrayList<Map<String, ?>> SAMPLES = new ArrayList<>();
+ static {
for (int i = 1; i < 25; i++) {
- add(make("List Item: " + i));
+ Map<String, Object> sample = new HashMap<String, Object>();
+ sample.put(KEY_NAME, "List Item: " + i);
+ SAMPLES.add(sample);
}
- }};
+ }
Handler mHandler = new Handler();
diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
index c11b0f3..f85fb0f 100644
--- a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
+++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
@@ -30,6 +30,7 @@
setContentView(tv);
}
+ @SuppressWarnings("ReturnValueIgnored")
@Override
public void onResume() {
((String) null).length();
diff --git a/tests/TouchLatency/Android.bp b/tests/TouchLatency/Android.bp
index 3a9e240..4ef1ead 100644
--- a/tests/TouchLatency/Android.bp
+++ b/tests/TouchLatency/Android.bp
@@ -12,6 +12,7 @@
manifest: "app/src/main/AndroidManifest.xml",
// omit gradle 'build' dir
srcs: ["app/src/main/java/**/*.java"],
+ static_libs: ["com.google.android.material_material"],
resource_dirs: ["app/src/main/res"],
aaptflags: ["--auto-add-overlay"],
sdk_version: "current",
diff --git a/tests/TouchLatency/app/build.gradle b/tests/TouchLatency/app/build.gradle
index f5ae6f4..129baab 100644
--- a/tests/TouchLatency/app/build.gradle
+++ b/tests/TouchLatency/app/build.gradle
@@ -6,7 +6,7 @@
defaultConfig {
applicationId "com.prefabulated.touchlatency"
- minSdkVersion 28
+ minSdkVersion 30
targetSdkVersion 33
versionCode 1
versionName "1.0"
@@ -17,4 +17,9 @@
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
+ dependencies {
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.6.0'
+ }
}
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 6ab3b3e..2e93c87 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -16,7 +16,6 @@
package com.prefabulated.touchlatency;
-import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Intent;
import android.hardware.display.DisplayManager;
@@ -30,25 +29,49 @@
import android.view.Window;
import android.view.WindowManager;
-public class TouchLatencyActivity extends Activity {
- private Mode mDisplayModes[];
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.material.slider.RangeSlider;
+import com.google.android.material.slider.RangeSlider.OnChangeListener;
+
+public class TouchLatencyActivity extends AppCompatActivity {
+ private static final int REFRESH_RATE_SLIDER_MIN = 20;
+ private static final int REFRESH_RATE_SLIDER_STEP = 5;
+
+ private Menu mMenu;
+ private Mode[] mDisplayModes;
private int mCurrentModeIndex;
+ private float mSliderPreferredRefreshRate;
private DisplayManager mDisplayManager;
+
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int i) {
- invalidateOptionsMenu();
+ updateOptionsMenu();
}
@Override
public void onDisplayRemoved(int i) {
- invalidateOptionsMenu();
+ updateOptionsMenu();
}
@Override
public void onDisplayChanged(int i) {
- invalidateOptionsMenu();
+ updateOptionsMenu();
+ }
+ };
+
+ private final RangeSlider.OnChangeListener mRefreshRateSliderListener = new OnChangeListener() {
+ @Override
+ public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) {
+ if (value == mSliderPreferredRefreshRate) return;
+
+ mSliderPreferredRefreshRate = value;
+ WindowManager.LayoutParams w = getWindow().getAttributes();
+ w.preferredRefreshRate = mSliderPreferredRefreshRate;
+ getWindow().setAttributes(w);
}
};
@@ -75,17 +98,23 @@
Trace.endSection();
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu");
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_touch_latency, menu);
+ public void updateOptionsMenu() {
if (mDisplayModes.length > 1) {
- MenuItem menuItem = menu.findItem(R.id.display_mode);
+ MenuItem menuItem = mMenu.findItem(R.id.display_mode);
Mode currentMode = getWindowManager().getDefaultDisplay().getMode();
updateDisplayMode(menuItem, currentMode);
}
- updateMultiDisplayMenu(menu.findItem(R.id.multi_display));
+ updateRefreshRateMenu(mMenu.findItem(R.id.frame_rate));
+ updateMultiDisplayMenu(mMenu.findItem(R.id.multi_display));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu");
+ mMenu = menu;
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_touch_latency, mMenu);
+ updateOptionsMenu();
Trace.endSection();
return true;
}
@@ -96,6 +125,32 @@
menuItem.setVisible(true);
}
+ private float getHighestRefreshRate() {
+ float maxRefreshRate = 0;
+ for (Display.Mode mode : getDisplay().getSupportedModes()) {
+ if (sameSizeMode(mode) && mode.getRefreshRate() > maxRefreshRate) {
+ maxRefreshRate = mode.getRefreshRate();
+ }
+ }
+ return maxRefreshRate;
+ }
+
+ private void updateRefreshRateMenu(MenuItem item) {
+ item.setActionView(R.layout.refresh_rate_layout);
+ RangeSlider slider = item.getActionView().findViewById(R.id.slider_from_layout);
+ slider.addOnChangeListener(mRefreshRateSliderListener);
+
+ float highestRefreshRate = getHighestRefreshRate();
+ slider.setValueFrom(REFRESH_RATE_SLIDER_MIN);
+ slider.setValueTo(highestRefreshRate);
+ slider.setStepSize(REFRESH_RATE_SLIDER_STEP);
+ if (mSliderPreferredRefreshRate < REFRESH_RATE_SLIDER_MIN
+ || mSliderPreferredRefreshRate > highestRefreshRate) {
+ mSliderPreferredRefreshRate = highestRefreshRate;
+ }
+ slider.setValues(mSliderPreferredRefreshRate);
+ }
+
private void updateMultiDisplayMenu(MenuItem item) {
item.setVisible(mDisplayManager.getDisplays().length > 1);
}
@@ -105,6 +160,12 @@
mDisplayManager.registerDisplayListener(mDisplayListener, new Handler());
}
+ private boolean sameSizeMode(Display.Mode mode) {
+ Mode currentMode = mDisplayModes[mCurrentModeIndex];
+ return currentMode.getPhysicalHeight() == mode.getPhysicalHeight()
+ && currentMode.getPhysicalWidth() == mode.getPhysicalWidth();
+ }
+
public void changeDisplayMode(MenuItem item) {
Window w = getWindow();
WindowManager.LayoutParams params = w.getAttributes();
@@ -112,10 +173,7 @@
int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length;
while (modeIndex != mCurrentModeIndex) {
// skip modes with different resolutions
- Mode currentMode = mDisplayModes[mCurrentModeIndex];
- Mode nextMode = mDisplayModes[modeIndex];
- if (currentMode.getPhysicalHeight() == nextMode.getPhysicalHeight()
- && currentMode.getPhysicalWidth() == nextMode.getPhysicalWidth()) {
+ if (sameSizeMode(mDisplayModes[modeIndex])) {
break;
}
modeIndex = (modeIndex + 1) % mDisplayModes.length;
diff --git a/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml
new file mode 100644
index 0000000..bb9ce60
--- /dev/null
+++ b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <com.google.android.material.slider.RangeSlider
+ android:id="@+id/slider_from_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:tickColor="@color/cardview_light_background"
+ app:trackColor="@color/cardview_light_background"
+ app:thumbColor="@color/cardview_dark_background"
+ android:visibility="visible"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
index abc7fd5..7169021 100644
--- a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
+++ b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
@@ -14,21 +14,25 @@
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".TouchLatencyActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="101"
- android:showAsAction="always"
- android:title="@string/mode"/>
+ android:title="@string/mode"
+ app:showAsAction="always" />
+ <item
+ android:id="@+id/frame_rate"
+ android:title="@string/frame_rate"
+ app:showAsAction="collapseActionView" />
<item
android:id="@+id/display_mode"
- android:showAsAction="ifRoom"
android:title="@string/display_mode"
- android:visible="false"/>
-
+ android:visible="false"
+ app:showAsAction="always" />
<item
android:id="@+id/multi_display"
- android:showAsAction="ifRoom"
android:title="@string/multi_display"
- android:visible="false"/>
+ android:visible="false"
+ app:showAsAction="ifRoom" />
</menu>
diff --git a/tests/TouchLatency/app/src/main/res/values/strings.xml b/tests/TouchLatency/app/src/main/res/values/strings.xml
index 5ee86d8..cad2df7 100644
--- a/tests/TouchLatency/app/src/main/res/values/strings.xml
+++ b/tests/TouchLatency/app/src/main/res/values/strings.xml
@@ -18,5 +18,6 @@
<string name="mode">Touch</string>
<string name="display_mode">Mode</string>
+ <string name="frame_rate">Frame Rate</string>
<string name="multi_display">multi-display</string>
</resources>
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index 22da7c1..b23a87e 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -16,7 +16,7 @@
<resources>
<!-- Base application theme. -->
- <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
+ <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
diff --git a/tests/TouchLatency/gradle.properties b/tests/TouchLatency/gradle.properties
index 1d3591c..ccd5dda 100644
--- a/tests/TouchLatency/gradle.properties
+++ b/tests/TouchLatency/gradle.properties
@@ -15,4 +15,5 @@
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
+# org.gradle.parallel=true
+android.useAndroidX=true
\ No newline at end of file
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index cbe13d9..650686f 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -373,6 +373,10 @@
try (InputStream is = new FileInputStream(certPath)) {
result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
}
+ // /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts
+ final String copiedCert = "/data/fonts/debug_cert.der";
+ runShellCommand("cp " + certPath + " " + copiedCert, null);
+ runShellCommand("cmd font install-debug-cert " + copiedCert, null);
// Assert that there are no errors.
assertThat(result.second).isEmpty();
String keyId = result.first.trim();
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 3da8b46..133c176 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -147,12 +147,39 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- return registerReceiver(receiver, filter, null, null);
+ return registerReceiver(receiver, filter, null, null, 0);
+ }
+
+ /**
+ * Registers the specified {@code receiver} to listen for broadcasts that match the {@code
+ * filter} in the current process.
+ *
+ * <p>Since this method only listens for broadcasts in the current process, the provided {@code
+ * flags} are ignored; this method is primarily intended to allow receivers that register with
+ * flags to register in the current process during tests.
+ */
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ return registerReceiver(receiver, filter, null, null, flags);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0);
+ }
+
+ /**
+ * Registers the specified {@code receiver} to listen for broadcasts that match the {@code
+ * filter} to run in the context of the specified {@code scheduler} in the current process.
+ *
+ * <p>Since this method only listens for broadcasts in the current process, the provided {@code
+ * flags} are ignored; this method is primarily intended to allow receivers that register with
+ * flags to register in the current process during tests.
+ */
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler, int flags) {
synchronized (mInterceptors) {
mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler));
}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index f924b2e..ad06830 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -637,8 +637,7 @@
final BroadcastReceiver receiver = getPackageChangeReceiver();
verify(mMockContext).registerReceiver(any(), argThat(filter -> {
- return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)
- && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
+ return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
}), any(), any());
receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED));
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 47750fc..4e597fb 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3743,15 +3743,15 @@
size_t amt = 0;
ResTable_entry header;
memset(&header, 0, sizeof(header));
- header.size = htods(sizeof(header));
+ header.full.size = htods(sizeof(header));
const type ty = mType;
if (ty == TYPE_BAG) {
- header.flags |= htods(header.FLAG_COMPLEX);
+ header.full.flags |= htods(header.FLAG_COMPLEX);
}
if (isPublic) {
- header.flags |= htods(header.FLAG_PUBLIC);
+ header.full.flags |= htods(header.FLAG_PUBLIC);
}
- header.key.index = htodl(mNameIndex);
+ header.full.key.index = htodl(mNameIndex);
if (ty != TYPE_BAG) {
status_t err = data->writeData(&header, sizeof(header));
if (err != NO_ERROR) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index f9e52b4..df87889 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -687,32 +687,32 @@
continue;
}
- printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]"
- : "[ResTable_entry]");
+ if (entry->is_complex()) {
+ printer_->Print("[ResTable_map_entry]");
+ } else if (entry->is_compact()) {
+ printer_->Print("[ResTable_entry_compact]");
+ } else {
+ printer_->Print("[ResTable_entry]");
+ }
+
printer_->Print(StringPrintf(" id: 0x%04x", it.index()));
printer_->Print(StringPrintf(
- " name: %s",
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index))
- .c_str()));
- printer_->Print(
- StringPrintf(" keyIndex: %u", android::util::DeviceToHost32(entry->key.index)));
- printer_->Print(StringPrintf(" size: %u", android::util::DeviceToHost32(entry->size)));
- printer_->Print(StringPrintf(" flags: 0x%04x", android::util::DeviceToHost32(entry->flags)));
+ " name: %s", android::util::GetString(key_pool_, entry->key()).c_str()));
+ printer_->Print(StringPrintf(" keyIndex: %u", entry->key()));
+ printer_->Print(StringPrintf(" size: %zu", entry->size()));
+ printer_->Print(StringPrintf(" flags: 0x%04x", entry->flags()));
printer_->Indent();
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- auto map_entry = (const ResTable_map_entry*)entry;
- printer_->Print(
- StringPrintf(" count: 0x%04x", android::util::DeviceToHost32(map_entry->count)));
+ if (auto map_entry = entry->map_entry()) {
+ uint32_t map_entry_count = android::util::DeviceToHost32(map_entry->count);
+ printer_->Print(StringPrintf(" count: 0x%04x", map_entry_count));
printer_->Print(StringPrintf(" parent: 0x%08x\n",
android::util::DeviceToHost32(map_entry->parent.ident)));
// Print the name and value mappings
- auto maps = (const ResTable_map*)((const uint8_t*)entry +
- android::util::DeviceToHost32(entry->size));
- for (size_t i = 0, count = android::util::DeviceToHost32(map_entry->count); i < count;
- i++) {
+ auto maps = (const ResTable_map*)((const uint8_t*)entry + entry->size());
+ for (size_t i = 0; i < map_entry_count; i++) {
PrintResValue(&(maps[i].value), config, type);
printer_->Print(StringPrintf(
@@ -725,9 +725,8 @@
printer_->Print("\n");
// Print the value of the entry
- auto value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- PrintResValue(value, config, type);
+ Res_value value = entry->value();
+ PrintResValue(&value, config, type);
}
printer_->Undent();
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 2a450ba..1d7fd1d 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -46,6 +46,13 @@
string version = 2;
}
+// References to non local resources
+message DynamicRefTable {
+ PackageId package_id = 1;
+ string package_name = 2;
+}
+
+
// Top level message representing a resource table.
message ResourceTable {
// The string pool containing source paths referenced throughout the resource table. This does
@@ -60,6 +67,8 @@
// The version fingerprints of the tools that built the resource table.
repeated ToolFingerprint tool_fingerprint = 4;
+
+ repeated DynamicRefTable dynamic_ref_table = 5;
}
// A package ID in the range [0x00, 0xff].
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 116dcd6..a8d2299 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1085,6 +1085,10 @@
const auto localeconfig_entry =
ResolveTableEntry(context_, &final_table_, localeconfig_reference);
if (!localeconfig_entry) {
+ // If locale config is resolved from external symbols - skip validation.
+ if (context_->GetExternalSymbols()->FindByReference(*localeconfig_reference)) {
+ return true;
+ }
context_->GetDiagnostics()->Error(
android::DiagMessage(localeConfig->compiled_value->GetSource())
<< "no localeConfig entry");
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index cffcdf2..5fdfb66 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -159,6 +159,9 @@
AddOptionalSwitch("--enable-sparse-encoding",
"This decreases APK size at the cost of resource retrieval performance.",
&options_.use_sparse_encoding);
+ AddOptionalSwitch("--enable-compact-entries",
+ "This decreases APK size by using compact resource entries for simple data types.",
+ &options_.table_flattener_options.use_compact_entries);
AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
&legacy_x_flag_);
AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 254f3a5..28fcc1a 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -840,6 +840,43 @@
ASSERT_TRUE(Link(link1_args, &diag));
}
+TEST_F(LinkTest, LocaleConfigVerificationExternalSymbol) {
+ StdErrDiagnostics diag;
+ const std::string base_files_dir = GetTestPath("base");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locales_config.xml"), R"(
+ <locale-config xmlns:android="http://schemas.android.com/apk/res/android">
+ <locale android:name="en-US"/>
+ <locale android:name="pt"/>
+ <locale android:name="es-419"/>
+ <locale android:name="zh-Hans-SG"/>
+ </locale-config>)",
+ base_files_dir, &diag));
+ const std::string base_apk = GetTestPath("base.apk");
+ std::vector<std::string> link_args = {
+ "--manifest",
+ GetDefaultManifest("com.aapt2.app"),
+ "-o",
+ base_apk,
+ };
+ ASSERT_TRUE(Link(link_args, base_files_dir, &diag));
+
+ const std::string localeconfig_manifest = GetTestPath("localeconfig_manifest.xml");
+ const std::string out_apk = GetTestPath("out.apk");
+ WriteFile(localeconfig_manifest, android::base::StringPrintf(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.aapt2.app">
+
+ <application
+ android:localeConfig="@xml/locales_config">
+ </application>
+ </manifest>)"));
+ link_args = LinkCommandBuilder(this)
+ .SetManifestFile(localeconfig_manifest)
+ .AddParameter("-I", base_apk)
+ .Build(out_apk);
+ ASSERT_TRUE(Link(link_args, &diag));
+}
+
TEST_F(LinkTest, LocaleConfigWrongTag) {
StdErrDiagnostics diag;
const std::string compiled_files_dir = GetTestPath("compiled");
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index d9e379d..8291862 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -384,21 +384,16 @@
continue;
}
- const ResourceName name(
- package->name, *parsed_type,
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index)));
+ const ResourceName name(package->name, *parsed_type,
+ android::util::GetString(key_pool_, entry->key()));
const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index()));
std::unique_ptr<Value> resource_value;
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-
+ if (auto mapEntry = entry->map_entry()) {
// TODO(adamlesinski): Check that the entry count is valid.
resource_value = ParseMapEntry(name, config, mapEntry);
} else {
- const Res_value* value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- resource_value = ParseValue(name, config, *value);
+ resource_value = ParseValue(name, config, entry->value());
}
if (!resource_value) {
@@ -419,7 +414,7 @@
.SetId(res_id, OnIdConflict::CREATE_ENTRY)
.SetAllowMangled(true);
- if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+ if (entry->flags() & ResTable_entry::FLAG_PUBLIC) {
Visibility visibility{Visibility::Level::kPublic};
auto spec_flags = entry_type_spec_flags_.find(res_id);
diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp
index 8832c24..9dc205f 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter.cpp
@@ -24,11 +24,6 @@
namespace aapt {
-using android::BigBuffer;
-using android::Res_value;
-using android::ResTable_entry;
-using android::ResTable_map;
-
struct less_style_entries {
bool operator()(const Style::Entry* a, const Style::Entry* b) const {
if (a->key.id) {
@@ -189,26 +184,40 @@
};
template <typename T>
-void WriteEntry(const FlatEntry* entry, T* out_result) {
+void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) {
static_assert(std::is_same_v<ResTable_entry, T> || std::is_same_v<ResTable_entry_ext, T>,
"T must be ResTable_entry or ResTable_entry_ext");
ResTable_entry* out_entry = (ResTable_entry*)out_result;
+ uint16_t flags = 0;
+
if (entry->entry->visibility.level == Visibility::Level::kPublic) {
- out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+ flags |= ResTable_entry::FLAG_PUBLIC;
}
if (entry->value->IsWeak()) {
- out_entry->flags |= ResTable_entry::FLAG_WEAK;
+ flags |= ResTable_entry::FLAG_WEAK;
}
if constexpr (std::is_same_v<ResTable_entry_ext, T>) {
- out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
+ flags |= ResTable_entry::FLAG_COMPLEX;
}
- out_entry->flags = android::util::HostToDevice16(out_entry->flags);
- out_entry->key.index = android::util::HostToDevice32(entry->entry_key);
- out_entry->size = android::util::HostToDevice16(sizeof(T));
+ if (!compact) {
+ out_entry->full.flags = android::util::HostToDevice16(flags);
+ out_entry->full.key.index = android::util::HostToDevice32(entry->entry_key);
+ out_entry->full.size = android::util::HostToDevice16(sizeof(T));
+ } else {
+ Res_value value;
+ CHECK(entry->entry_key < 0xffffu) << "cannot encode key in 16-bit";
+ CHECK(compact && (std::is_same_v<ResTable_entry, T>)) << "cannot encode complex entry";
+ CHECK(ValueCast<Item>(entry->value)->Flatten(&value)) << "flatten failed";
+
+ flags |= ResTable_entry::FLAG_COMPACT | (value.dataType << 8);
+ out_entry->compact.flags = android::util::HostToDevice16(flags);
+ out_entry->compact.key = android::util::HostToDevice16(entry->entry_key);
+ out_entry->compact.data = value.data;
+ }
}
int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) {
@@ -222,57 +231,26 @@
return offset;
}
-void WriteItemToPair(const FlatEntry* item_entry, ResEntryValuePair* out_pair) {
- static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
- "ResEntryValuePair must not have padding between entry and value.");
+template <bool compact_entry, typename T>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer) {
+ int32_t offset = buffer->size();
+ T* out_entry = buffer->NextBlock<T>();
- WriteEntry<ResTable_entry>(item_entry, &out_pair->entry);
-
- CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_pair->value)) << "flatten failed";
- out_pair->value.size = android::util::HostToDevice16(sizeof(out_pair->value));
-}
-
-int32_t SequentialResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t SequentialResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t offset = entries_buffer_->size();
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
- return offset;
-}
-
-std::size_t ResEntryValuePairContentHasher::operator()(const ResEntryValuePairRef& ref) const {
- return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(ResEntryValuePair));
-}
-
-bool ResEntryValuePairContentEqualTo::operator()(const ResEntryValuePairRef& a,
- const ResEntryValuePairRef& b) const {
- return std::memcmp(a.ptr, b.ptr, sizeof(ResEntryValuePair)) == 0;
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t initial_offset = entries_buffer_->size();
-
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
-
- auto ref = ResEntryValuePairRef{*out_pair};
- auto [it, inserted] = entry_offsets.insert({ref, initial_offset});
- if (inserted) {
- // If inserted just return a new offset as this is a first time we store
- // this entry.
- return initial_offset;
+ if constexpr (compact_entry) {
+ WriteEntry(item_entry, out_entry, true);
+ } else {
+ WriteEntry(item_entry, &out_entry->entry);
+ CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_entry->value)) << "flatten failed";
+ out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value));
}
- // If not inserted this means that this is a duplicate, backup allocated block to the buffer
- // and return offset of previously stored entry.
- entries_buffer_->BackUp(sizeof(ResEntryValuePair));
- return it->second;
+ return {offset, out_entry};
}
-} // namespace aapt
\ No newline at end of file
+// explicitly specialize both versions
+template std::pair<int32_t, ResEntryValue<false>*> WriteItemToBuffer<false>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
+
+template std::pair<int32_t, ResEntryValue<true>*> WriteItemToBuffer<true>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
+
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h
index a36ceec..c11598e 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.h
+++ b/tools/aapt2/format/binary/ResEntryWriter.h
@@ -27,6 +27,11 @@
namespace aapt {
+using android::BigBuffer;
+using android::Res_value;
+using android::ResTable_entry;
+using android::ResTable_map;
+
struct FlatEntry {
const ResourceTableEntryView* entry;
const Value* value;
@@ -39,28 +44,42 @@
// We introduce this structure for ResEntryWriter to a have single allocation using
// BigBuffer::NextBlock which allows to return it back with BigBuffer::Backup.
struct ResEntryValuePair {
- android::ResTable_entry entry;
- android::Res_value value;
+ ResTable_entry entry;
+ Res_value value;
};
-// References ResEntryValuePair object stored in BigBuffer used as a key in std::unordered_map.
-// Allows access to memory address where ResEntryValuePair is stored.
-union ResEntryValuePairRef {
- const std::reference_wrapper<const ResEntryValuePair> pair;
+static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
+ "ResEntryValuePair must not have padding between entry and value.");
+
+template <bool compact>
+using ResEntryValue = std::conditional_t<compact, ResTable_entry, ResEntryValuePair>;
+
+// References ResEntryValue object stored in BigBuffer used as a key in std::unordered_map.
+// Allows access to memory address where ResEntryValue is stored.
+template <bool compact>
+union ResEntryValueRef {
+ using T = ResEntryValue<compact>;
+ const std::reference_wrapper<const T> ref;
const u_char* ptr;
- explicit ResEntryValuePairRef(const ResEntryValuePair& ref) : pair(ref) {
+ explicit ResEntryValueRef(const T& rev) : ref(rev) {
}
};
-// Hasher which computes hash of ResEntryValuePair using its bytes representation in memory.
-struct ResEntryValuePairContentHasher {
- std::size_t operator()(const ResEntryValuePairRef& ref) const;
+// Hasher which computes hash of ResEntryValue using its bytes representation in memory.
+struct ResEntryValueContentHasher {
+ template <typename R>
+ std::size_t operator()(const R& ref) const {
+ return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(typename R::T));
+ }
};
// Equaler which compares ResEntryValuePairs using theirs bytes representation in memory.
-struct ResEntryValuePairContentEqualTo {
- bool operator()(const ResEntryValuePairRef& a, const ResEntryValuePairRef& b) const;
+struct ResEntryValueContentEqualTo {
+ template <typename R>
+ bool operator()(const R& a, const R& b) const {
+ return std::memcmp(a.ptr, b.ptr, sizeof(typename R::T)) == 0;
+ }
};
// Base class that allows to write FlatEntries into entries_buffer.
@@ -79,9 +98,9 @@
}
protected:
- ResEntryWriter(android::BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
+ ResEntryWriter(BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
}
- android::BigBuffer* entries_buffer_;
+ BigBuffer* entries_buffer_;
virtual int32_t WriteItem(const FlatEntry* entry) = 0;
@@ -91,18 +110,29 @@
DISALLOW_COPY_AND_ASSIGN(ResEntryWriter);
};
+int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer);
+
+template <bool compact_entry, typename T=ResEntryValue<compact_entry>>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer);
+
// ResEntryWriter which writes FlatEntries sequentially into entries_buffer.
// Next entry is always written right after previous one in the buffer.
+template <bool compact_entry = false>
class SequentialResEntryWriter : public ResEntryWriter {
public:
- explicit SequentialResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit SequentialResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~SequentialResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ auto result = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
+ return result.first;
+ }
- int32_t WriteMap(const FlatEntry* entry) override;
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(SequentialResEntryWriter);
@@ -111,25 +141,44 @@
// ResEntryWriter that writes only unique entry and value pairs into entries_buffer.
// Next entry is written into buffer only if there is no entry with the same bytes representation
// in memory written before. Otherwise returns offset of already written entry.
+template <bool compact_entry = false>
class DeduplicateItemsResEntryWriter : public ResEntryWriter {
public:
- explicit DeduplicateItemsResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit DeduplicateItemsResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~DeduplicateItemsResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ const auto& [offset, out_entry] = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
- int32_t WriteMap(const FlatEntry* entry) override;
+ auto [it, inserted] = entry_offsets.insert({Ref{*out_entry}, offset});
+ if (inserted) {
+ // If inserted just return a new offset as this is a first time we store
+ // this entry
+ return offset;
+ }
+
+ // If not inserted this means that this is a duplicate, backup allocated block to the buffer
+ // and return offset of previously stored entry
+ entries_buffer_->BackUp(sizeof(*out_entry));
+ return it->second;
+ }
+
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(DeduplicateItemsResEntryWriter);
- std::unordered_map<ResEntryValuePairRef, int32_t, ResEntryValuePairContentHasher,
- ResEntryValuePairContentEqualTo>
- entry_offsets;
+ using Ref = ResEntryValueRef<compact_entry>;
+ using Map = std::unordered_map<Ref, int32_t,
+ ResEntryValueContentHasher,
+ ResEntryValueContentEqualTo>;
+ Map entry_offsets;
};
} // namespace aapt
-#endif
\ No newline at end of file
+#endif
diff --git a/tools/aapt2/format/binary/ResEntryWriter_test.cpp b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
index 56ca133..4cb17c3 100644
--- a/tools/aapt2/format/binary/ResEntryWriter_test.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
@@ -56,14 +56,28 @@
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
- 2 * sizeof(ResEntryValuePair)};
- EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
+ 2 * sizeof(ResEntryValuePair)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry),
+ 2 * sizeof(ResTable_entry)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -83,13 +97,26 @@
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
@@ -100,13 +127,26 @@
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, 0, 0};
- EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -126,13 +166,26 @@
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
-};
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+ };
} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 318b8b6..f192234 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -16,6 +16,7 @@
#include "format/binary/TableFlattener.h"
+#include <limits>
#include <sstream>
#include <type_traits>
#include <variant>
@@ -67,7 +68,9 @@
public:
PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
const std::map<size_t, std::string>* shared_libs,
- SparseEntriesMode sparse_entries, bool collapse_key_stringpool,
+ SparseEntriesMode sparse_entries,
+ bool compact_entries,
+ bool collapse_key_stringpool,
const std::set<ResourceName>& name_collapse_exemptions,
bool deduplicate_entry_values)
: context_(context),
@@ -75,6 +78,7 @@
package_(package),
shared_libs_(shared_libs),
sparse_entries_(sparse_entries),
+ compact_entries_(compact_entries),
collapse_key_stringpool_(collapse_key_stringpool),
name_collapse_exemptions_(name_collapse_exemptions),
deduplicate_entry_values_(deduplicate_entry_values) {
@@ -135,6 +139,33 @@
private:
DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
+ // Use compact entries only if
+ // 1) it is enabled, and that
+ // 2) the entries will be accessed on platforms U+, and
+ // 3) all entry keys can be encoded in 16 bits
+ bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const {
+ return compact_entries_ &&
+ (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) &&
+ std::none_of(entries->cbegin(), entries->cend(),
+ [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
+ }
+
+ std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) {
+ if (dedup) {
+ if (compact) {
+ return std::make_unique<DeduplicateItemsResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<DeduplicateItemsResEntryWriter<false>>(buffer);
+ }
+ } else {
+ if (compact) {
+ return std::make_unique<SequentialResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<SequentialResEntryWriter<false>>(buffer);
+ }
+ }
+ }
+
bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config,
const size_t num_total_entries, std::vector<FlatEntry>* entries,
BigBuffer* buffer) {
@@ -150,21 +181,20 @@
std::vector<uint32_t> offsets;
offsets.resize(num_total_entries, 0xffffffffu);
+ bool compact_entry = UseCompactEntries(config, entries);
+
android::BigBuffer values_buffer(512);
- std::variant<std::monostate, DeduplicateItemsResEntryWriter, SequentialResEntryWriter>
- writer_variant;
- ResEntryWriter* res_entry_writer;
- if (deduplicate_entry_values_) {
- res_entry_writer = &writer_variant.emplace<DeduplicateItemsResEntryWriter>(&values_buffer);
- } else {
- res_entry_writer = &writer_variant.emplace<SequentialResEntryWriter>(&values_buffer);
- }
+ auto res_entry_writer = GetResEntryWriter(deduplicate_entry_values_,
+ compact_entry, &values_buffer);
for (FlatEntry& flat_entry : *entries) {
CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
offsets[flat_entry.entry->id.value()] = res_entry_writer->Write(&flat_entry);
}
+ // whether the offsets can be represented in 2 bytes
+ bool short_offsets = (values_buffer.size() / 4u) < std::numeric_limits<uint16_t>::max();
+
bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled ||
sparse_entries_ == SparseEntriesMode::Forced;
@@ -177,8 +207,7 @@
}
// Only sparse encode if the offsets are representable in 2 bytes.
- sparse_encode =
- sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
+ sparse_encode = sparse_encode && short_offsets;
// Only sparse encode if the ratio of populated entries to total entries is below some
// threshold.
@@ -200,12 +229,22 @@
}
} else {
type_header->entryCount = android::util::HostToDevice32(num_total_entries);
- uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
- for (size_t i = 0; i < num_total_entries; i++) {
- indices[i] = android::util::HostToDevice32(offsets[i]);
+ if (compact_entry && short_offsets) {
+ // use 16-bit offset only when compact_entry is true
+ type_header->flags |= ResTable_type::FLAG_OFFSET16;
+ uint16_t* indices = type_writer.NextBlock<uint16_t>(num_total_entries);
+ for (size_t i = 0; i < num_total_entries; i++) {
+ indices[i] = android::util::HostToDevice16(offsets[i] / 4u);
+ }
+ } else {
+ uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
+ for (size_t i = 0; i < num_total_entries; i++) {
+ indices[i] = android::util::HostToDevice32(offsets[i]);
+ }
}
}
+ type_writer.buffer()->Align4();
type_header->entriesStart = android::util::HostToDevice32(type_writer.size());
type_writer.buffer()->AppendBuffer(std::move(values_buffer));
type_writer.Finish();
@@ -512,6 +551,7 @@
const ResourceTablePackageView package_;
const std::map<size_t, std::string>* shared_libs_;
SparseEntriesMode sparse_entries_;
+ bool compact_entries_;
android::StringPool type_pool_;
android::StringPool key_pool_;
bool collapse_key_stringpool_;
@@ -568,7 +608,9 @@
}
PackageFlattener flattener(context, package, &table->included_packages_,
- options_.sparse_entries, options_.collapse_key_stringpool,
+ options_.sparse_entries,
+ options_.use_compact_entries,
+ options_.collapse_key_stringpool,
options_.name_collapse_exemptions,
options_.deduplicate_entry_values);
if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 6151b7e..35254ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -44,6 +44,9 @@
// as a sparse map of entry ID and offset to actual data.
SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
+ // When true, use compact entries for simple data
+ bool use_compact_entries = false;
+
// When true, the key string pool in the final ResTable
// is collapsed to a single entry. All resource entries
// have name indices that point to this single value
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 6a1e8c1..e39f327 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -562,6 +562,11 @@
}
}
+ for (const pb::DynamicRefTable& dynamic_ref : pb_table.dynamic_ref_table()) {
+ out_table->included_packages_.insert(
+ {dynamic_ref.package_id().id(), dynamic_ref.package_name()});
+ }
+
// Deserialize the overlayable groups of the table
std::vector<std::shared_ptr<Overlayable>> overlayables;
for (const pb::Overlayable& pb_overlayable : pb_table.overlayable()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 163a60a..a6d58fd 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -345,7 +345,11 @@
pb::ToolFingerprint* pb_fingerprint = out_table->add_tool_fingerprint();
pb_fingerprint->set_tool(util::GetToolName());
pb_fingerprint->set_version(util::GetToolFingerprint());
-
+ for (auto it = table.included_packages_.begin(); it != table.included_packages_.end(); ++it) {
+ pb::DynamicRefTable* pb_dynamic_ref = out_table->add_dynamic_ref_table();
+ pb_dynamic_ref->mutable_package_id()->set_id(it->first);
+ pb_dynamic_ref->set_package_name(it->second);
+ }
std::vector<Overlayable*> overlayables;
auto table_view = table.GetPartitionedView();
for (const auto& package : table_view.packages) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 692fa42..5adc5e6 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -1024,4 +1024,28 @@
EXPECT_THAT(*(custom_layout->path), Eq("res/layout/bar.xml"));
}
+TEST(ProtoSerializeTest, SerializeDynamicRef) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
+ table->included_packages_.insert({20, "foobar"});
+ table->included_packages_.insert({30, "barfoo"});
+
+ ResourceTable new_table;
+ pb::ResourceTable pb_table;
+ MockFileCollection files;
+ std::string error;
+ SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+ EXPECT_THAT(error, IsEmpty());
+
+ int result = new_table.included_packages_.size();
+ EXPECT_THAT(result, Eq(2));
+ auto it = new_table.included_packages_.begin();
+ EXPECT_THAT(it->first, Eq(20));
+ EXPECT_THAT(it->second, Eq("foobar"));
+ it++;
+ EXPECT_THAT(it->first, Eq(30));
+ EXPECT_THAT(it->second, Eq("barfoo"));
+}
} // namespace aapt
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index 87b4c68..006a0290 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -54,6 +54,7 @@
'or': 'Orya',
'pa': 'Guru',
'pt': 'Latn',
+ 'pl': 'Latn',
'ru': 'Latn',
'sk': 'Latn',
'sl': 'Latn',
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 4d69d26..741655b 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -20,6 +20,7 @@
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
import com.google.android.lint.aidl.ManualPermissionCheckDetector
import com.google.android.lint.parcel.SaferParcelChecker
import com.google.auto.service.AutoService
@@ -38,6 +39,7 @@
CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+ EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
new file mode 100644
index 0000000..3c2ea1d
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.google.android.lint.aidl
+
+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.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiElement
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+ listOf(UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+ private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+ val targetExpression = "super.${node.name}$HELPER_SUFFIX()"
+
+ val body = node.uastBody as? UBlockExpression
+ if (body == null) {
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ "Method must start with $targetExpression",
+ )
+ return
+ }
+
+ val firstExpression = body.expressions.firstOrNull()
+ if (firstExpression == null) {
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ "Method must start with $targetExpression",
+ )
+ return
+ }
+
+ val firstExpressionSource = firstExpression.asSourceString()
+ .filterNot(Char::isWhitespace)
+
+ if (firstExpressionSource != targetExpression) {
+ val locationTarget = getLocationTarget(firstExpression)
+ val expressionLocation = context.getLocation(locationTarget)
+ val indent = " ".repeat(expressionLocation.start?.column ?: 0)
+
+ val fix = fix()
+ .replace()
+ .range(expressionLocation)
+ .beginning()
+ .with("$targetExpression;\n\n$indent")
+ .reformat(true)
+ .autoFix()
+ .build()
+
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ "Method must start with $targetExpression",
+ fix
+ )
+ }
+ }
+ }
+
+ companion object {
+ private const val HELPER_SUFFIX = "_enforcePermission"
+
+ private const val EXPLANATION = """
+ When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the
+ permission check called yourMethodName$HELPER_SUFFIX.
+
+ You must call this method as the first expression in your implementation.
+ """
+
+ val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+ id = "MissingEnforcePermissionHelper",
+ briefDescription = """Missing permission-enforcing method call in AIDL method
+ |annotated with @EnforcePermission""".trimMargin(),
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ EnforcePermissionHelperDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ /**
+ * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+ * resulting in an incorrect Location if used directly
+ */
+ private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+ if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+ if (firstExpression is UDeclarationsExpression) {
+ return firstExpression.declarations.firstOrNull()?.sourcePsi
+ }
+ return null
+ }
+ }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
new file mode 100644
index 0000000..31e4846
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -0,0 +1,159 @@
+/*
+* 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+
+class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
+ override fun getDetector() = EnforcePermissionHelperDetector()
+ override fun getIssues() = listOf(
+ EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER)
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+ fun testFirstExpressionIsFunctionCall() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ Binder.getCallingUid();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+ @@ -8 +8
+ + super.test_enforcePermission();
+ +
+ """
+ )
+ }
+
+ fun testFirstExpressionIsVariableDeclaration() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ String foo = "bar";
+ Binder.getCallingUid();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+ @@ -8 +8
+ + super.test_enforcePermission();
+ +
+ """
+ )
+ }
+
+ fun testMethodIsEmpty() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {}
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testOkay() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ companion object {
+ val stubs = arrayOf(aidlStub, contextStub, binderStub)
+ }
+}
+
+
+
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
index a968f5e..d4a3497 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -17,7 +17,6 @@
package com.google.android.lint.aidl
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
@@ -361,82 +360,6 @@
companion object {
- private val aidlStub: TestFile = java(
- """
- package android.test;
- public interface ITest extends android.os.IInterface {
- public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
- public void test() throws android.os.RemoteException;
- }
- """
- ).indented()
-
- private val contextStub: TestFile = java(
- """
- package android.content;
- public class Context {
- @android.content.pm.PermissionMethod
- public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
- }
- """
- ).indented()
-
- private val binderStub: TestFile = java(
- """
- package android.os;
- public class Binder {
- public static int getCallingUid() {}
- }
- """
- ).indented()
-
- private val permissionMethodStub: TestFile = java(
- """
- package android.content.pm;
-
- import static java.lang.annotation.ElementType.METHOD;
- import static java.lang.annotation.RetentionPolicy.CLASS;
-
- import java.lang.annotation.Retention;
- import java.lang.annotation.Target;
-
- @Retention(CLASS)
- @Target({METHOD})
- public @interface PermissionMethod {}
- """
- ).indented()
-
- private val permissionNameStub: TestFile = java(
- """
- package android.content.pm;
-
- import static java.lang.annotation.ElementType.FIELD;
- import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
- import static java.lang.annotation.ElementType.METHOD;
- import static java.lang.annotation.ElementType.PARAMETER;
- import static java.lang.annotation.RetentionPolicy.CLASS;
-
- import java.lang.annotation.Retention;
- import java.lang.annotation.Target;
-
- @Retention(CLASS)
- @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
- public @interface PermissionName {}
- """
- ).indented()
-
- private val manifestStub: TestFile = java(
- """
- package android;
-
- public final class Manifest {
- public static final class permission {
- public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
- }
- }
- """.trimIndent()
- )
-
val stubs = arrayOf(
aidlStub,
contextStub,
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
new file mode 100644
index 0000000..bd6b195
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -0,0 +1,80 @@
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFile
+
+val aidlStub: TestFile = java(
+ """
+ package android.test;
+ public interface ITest extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+ public void test() throws android.os.RemoteException;
+ }
+ """
+).indented()
+
+val contextStub: TestFile = java(
+ """
+ package android.content;
+ public class Context {
+ @android.content.pm.PermissionMethod
+ public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
+ }
+ """
+).indented()
+
+val binderStub: TestFile = java(
+ """
+ package android.os;
+ public class Binder {
+ public static int getCallingUid() {}
+ }
+ """
+).indented()
+
+val permissionMethodStub: TestFile = java(
+"""
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({METHOD})
+ public @interface PermissionMethod {}
+ """
+).indented()
+
+val permissionNameStub: TestFile = java(
+"""
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.FIELD;
+ import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.ElementType.PARAMETER;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+ public @interface PermissionName {}
+ """
+).indented()
+
+val manifestStub: TestFile = java(
+ """
+ package android;
+
+ public final class Manifest {
+ public static final class permission {
+ public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
+ }
+ }
+ """.trimIndent()
+)
\ No newline at end of file
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
index 6efd1f6..ff1f8e2 100644
--- a/tools/locked_region_code_injection/Android.bp
+++ b/tools/locked_region_code_injection/Android.bp
@@ -16,6 +16,6 @@
"asm-commons-9.2",
"asm-tree-9.2",
"asm-analysis-9.2",
- "guava-21.0",
+ "guava",
],
}
diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp
index 39d1b1c..bb32df6 100644
--- a/tools/traceinjection/Android.bp
+++ b/tools/traceinjection/Android.bp
@@ -16,7 +16,7 @@
"asm-commons-9.2",
"asm-tree-9.2",
"asm-analysis-9.2",
- "guava-21.0",
+ "guava",
],
}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index a750696..5012622 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -117,13 +117,12 @@
private static final byte[] TEST_PSK =
new byte[]{'T', 'e', 's', 't'};
- private static final Set<Integer> SCAN_FREQ_SET =
- new HashSet<Integer>() {{
- add(2410);
- add(2450);
- add(5050);
- add(5200);
- }};
+ private static final Set<Integer> SCAN_FREQ_SET = Set.of(
+ 2410,
+ 2450,
+ 5050,
+ 5200);
+
private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
private static final int[] TEST_FREQUENCIES_1 = {};
@@ -131,13 +130,11 @@
private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
- private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
- new ArrayList<byte[]>() {{
- add(LocalNativeUtil.byteArrayFromArrayList(
- LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
- add(LocalNativeUtil.byteArrayFromArrayList(
- LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
- }};
+ private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST = List.of(
+ LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)),
+ LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
private static final PnoSettings TEST_PNO_SETTINGS = new PnoSettings();
static {